Supplementary Information for

‘A Devonian Fish Tale: A New Method of Body Length Estimation Suggests Much Smaller Sizes for Dunkleosteus terrelli (Placodermi: Arthrodira)’

Russell Engelman

1/28/2022

Important Note: If the R code (.rmd file) does not work when trying to rerun the analysis, one issue may be that the datasets (the .xlsx and/or .csv files) and additional images (i.e., the .png files) must be in the same working environment (usually the same folder) as the .rmd file. If possible, make sure the supplementary figures are in the same folder as the R Code. In the event that this is not possible, deactivate the chunks that contain references to supplementary figures or delete these lines from the R Code.

1 Loading in data

#Ingesting data
data_initial<-readxl::read_excel("Devonian Fish Tale Supplementary File 1 (Data).xlsx")%>%
  filter(is.na(drop)) %>%
  rename(clade=Clade,higher_group="Higher Group",order=Order,family=Family,
         extinct=Extinct,shape=Shape,habitat=Habitat,
         total_length="Total Length (cm)",precaudal_length="Precaudal Length (cm)",
         body_mass="Body Mass (g)",head_length="Head Length (cm)",
         snout_length="Snout Length (cm)",OOL="OOL (cm)",
         body_width="Mediolateral Body Width (cm)",
         body_depth="Dorsoventral Body Height (cm)",
         genus=Genus,species=Species, specimen=Specimen,length_as="Length As",
         fork_length="Fork Length (cm)",girth="Girth (cm)",
         prepectoral_length="Prepectoral Length (cm)",
         prepelvic_length="Prepelvic Length (cm)",
         mouth_width="Mouth Width (cm)",head_depth="Head Height (cm)",
         swimbladder="Presence of Swimbladder",
         heterocercal="Presence of Heterocercal Caudal Fin",
         ontogeny="Ontogenetic Status",references="Reference",
         is_body_mass_estimated="Is Body Mass Estimated")%>%
  mutate(taxon = paste0(genus,"_",species),
         shape=relevel(factor(shape),"fusiform"),
         habitat=relevel(factor(habitat),"demersal"))

#Filtering out known juveniles from the final dataset
data_final<-data_initial%>%
  filter(ontogeny %in% c("adult","subadult")|is.na(ontogeny)|clade=="Placodermi")

fossil.specimens<-quo(clade=="Placodermi"&length_as=="total length"|specimen %in% c("CMNH 7424","CMNH 6090","CMNH 7054","CMNH 5768"))

1.1 Separating out fossil taxa

(fossil_taxa<-data_initial%>%
  filter(clade=="Placodermi")%>%
  mutate(specimen2=paste0(genus," ",specimen),
         taxon=str_replace(taxon,"_"," "))%>%
  column_to_rownames("specimen2")%>%
  arrange(head_length)%>%
  mutate(taxon=ifelse(genus=="Newspecies","Gen. et sp. nov.",taxon)))

1.2 Calculating species-average model

data_species<-data_final%>%
  drop_na(total_length,OOL)%>%
  filter(length_as!="estimated t.l.")%>%
  mutate(N=ifelse(is.na(N),as.numeric(N),1))%>%
  group_by(taxon)%>%
  summarise(across(where(is.numeric), weighted.mean, N),
            N=sum(N),genus=unique(genus),species=unique(species),
            clade=unique(clade),higher_group=unique(higher_group),
            order=unique(order),family=unique(family),
            shape=unique(shape),habitat=unique(habitat))%>%
  select(taxon,N,total_length,OOL,everything())

write.csv(data_species,"data_species.csv",row.names=F)

2 Code book

2.1 Measurements

Measurement Variable Name Definition
Total length total_length Length from anterior tip of rostrum to posterior tip of caudal fin
Fork length fork_length Length from anterior tip of rostrum to notch in caudal fork between upper and lower caudal fin lobes. Coded as NA for taxa without a caudal notch.
Standard length precaudal_length Length from anterior tip of rostrum to posterior tip of caudal peduncle
Head length head_length Length from anterior tip of rostrum to posterior margin of branchial cavity
For sharks: from snout to opening of terminal gill arch
For osteichthyans: from snout to posterior end of operculum
For arthrodires: from snout to cranio-thoracic joint
Snout length snout_length Length from anterior tip of rostrum to anterior margin of orbit
Orbit-opercle length (OOL) OOL Length from anterior margin of the orbit to posterior margin of the branchial cavity (see head_length for more details). Equivalent to head length minus snout length
Head depth head_depth Height of head from dorsal tip of supraoccipital to ventral margin of branchials.1
Body height body_depth Greatest dorsoventral height of the body
Body width body_width Greatest mediolateral width of the body
Girth girth greatest circumference of the body in transverse view
Pre-pectoral length prepectoral_length Length from tip of snout to origin of pectoral fin along anteroposterior axis
Pre-pelvic length prepelvic_length Length from tip of snout to origin of pelvic fin along anteroposterior axis
Bill length bill_length Length that the bill protrudes anterior to the mouth in Istiophoriformes and Hemirhamphidae. This value was subtracted from total length and all other measurements that measured from the tip of the snout before the start of the study.

1 - Note that when possible head_depth in chondrichthyans was measured at the posterior margin of the chondrocranium to allow for more direct comparisons with other fishes, but most prior authors follow Compagno (1984) in taking this measurement as the depth at the origin of the pectoral fin.

2.2 Definitions for habitus

Term Definition
Benthic Spend large amounts of time on the bottom of bodies of water and are often poor or infrequent swimmers
Demersal Active swimmers, but typically live close to the sea floor. Often live in and around complex environments like underwater vegetation, coral reefs, or rock crevices
Neritic Active swimmers in the water column, but live in shallower waters closer to the shore . When associated with complex habitats like reefs, rock outcrops, or seaweed beds are typically found above the structure rather than living within it
Pelagic Inhabits open waters far from any shoreline, such as the open ocean or the open waters of large rivers or lakes. Often highly migratory or oceanodromus

Notably, these states are an attempt to categorize a very broad ecological spectrum that goes from benthic ↔︎ demersal ↔︎ neritic ↔︎ pelagic, and there are many species that could be argued to straddle some of these boundaries either within a single life stage (e.g., Sphyraena could be considered neritic/pelagic) or at different parts of their lifecycle (e.g., diadromous fishes like salmon). Additionally, many fishes are described as “benthopelagic”, which essentially provides almost no useful information as to their habits. “Benthopelagic” fishes could be considered as analogous to demeral but in practice seems to encompass demersal and clearly neritic fishes (e.g., many carangids like Seriola). Adding to this confusion, studies of freshwater fishes that are active in the water column consider them all to be “pelagic”, whereas researchers on marine fishes with analogous habits will often describe the same species as “benthopelagic” and treat them as equivalent to demersal species (e.g., this is a common issue when browsing the species profiles on FishBase). This makes it exceedingly hard to make comparisons of habits between freshwater and marine fishes.

As a result, in this study habitat was determined based on which of the above categories fishes most closely conformed to based on their natural history. However, the author wishes to note here how some of these habitat designations may need to be revised, due to the general confusion regarding life habit classifications in fishes.

2.3 Definitions for shape

Measurement Definition Example
fusiform Generalized fish body plan with a “torpedo-like” body shape rainbow trout (Oncorhynchus mykiss)
elongate Trunk is anteroposteriorly elongate but not snake-like or truly anguilliform barracuda (Sphyraena spp.)
anguilliform Extremely elongate, snake-like body, often with reduced fins American eel (Anguilla rostrata)
compressiform Body is anteroposteriorly short and deep relative to its length, often discoid bluegill (Lepomis macrochirus)
macruriform Precaudal portion is normal but has a long, straight “whip-like” or “lizard-like” tail, in contrast to a heterocercal or homocercal caudal fin. Alopias and Stegostoma are treated as macruriform here because of similarities in body construction (specifically, a long, whip-like caudal fin that results in OOL dramatically underestimating body length). chimaeras (Chimaeridae)

Defining the elongate category for sharks was a bit difficult. There are some sharks that very clearly exhibit elongated body plans (e.g., Isistius, Scyliorhinidae, Hemiscyllidae, Etmopterus), but a number of other shark groups exhibit an ambiguous condition (Mustelus, Hemigaleus spp.). This makes it difficult to code body shape, especially because sharks in general exhibit a “stretched out” body plan compared to other fishes (see manuscript). However, comparisons to ray-finned fishes (in terms of body depth/precaudal length and the residuals of the OOL model) suggest that at least some of these shark lineages do exhibit trunk proportions similar to elongate ray-finned fishes, and treating them as fusiform is statistically inappropriate. Therefore, a shark was considered to be elongate in body shape if it appeared to have a much more elongate trunk than is typical for an average shark-like body shape (e.g., Carcharhinus, Squalus)

2.4 Taxonomic distribution of observations

data_final%>%
  drop_na(OOL,total_length)%>%
  filter(length_as!="estimated t.l.")%>%
  group_by(clade)%>%
  summarise(N=n(),Taxa=n_distinct(taxon))%>%
  add_row(clade="All Species",
          N=data_final%>%drop_na(OOL,total_length)%>%
            filter(length_as!="estimated t.l.")%>%
            summarise(N=n())%>%pull(),
          Taxa=data_final%>%drop_na(OOL,total_length)%>%
            filter(length_as!="estimated t.l.")%>%
            summarise(Taxa=n_distinct(taxon))%>%pull())%>%
  kable(col.names=c("Clade","# Occurences","# Taxa"),
        align=c("l","c","c"),
        caption="Taxonomic distribution of observations made in this study")%>%
  row_spec(6, bold = T)%>%
  kable_styling()
Table 2.1: Taxonomic distribution of observations made in this study
Clade # Occurences # Taxa
Actinopterygii 2210 769
Chondrichthyes 568 181
Petromyzontiformes 358 8
Placodermi 17 10
Sarcopterygii 16 3
All Species 3169 971

2.5 Final cleaned dataset

data_final%>%
  arrange(clade,higher_group,order,family,genus,species)

2.6 Saving cleaned dataset

write.csv(data_final,file="ool_data_cleaned.csv")

3 Correlation of head and body proportions

grid.arrange(ncol=2,
  ggplot(data_final%>%filter(!is.na(head_depth),length_as=="total length"),
         aes(x=body_depth/total_length,y=head_depth/head_length))+
    geom_point(aes(color=clade,shape=clade))+
    geom_smooth(formula=y~x,method="lm")+
    scale_x_continuous(labels=scales::label_percent(accuracy = 1))+
    scale_y_continuous(labels=scales::label_percent(accuracy = 1))+
    ggtitle("A")+
    theme(legend.position="none"),
  ggplot(data_final%>%filter(!is.na(head_depth),length_as=="total length"),
         aes(x=body_depth/total_length,y=body_depth/head_length))+
    geom_point(aes(color=clade,shape=clade))+
    geom_smooth(formula=y~x,method="lm")+
    scale_x_continuous(labels=scales::label_percent(accuracy = 1))+
    scale_y_continuous(labels=scales::label_percent(accuracy = 1))+
    ggtitle("B")+
    theme(legend.position="none"),
  ggplot(data_final%>%filter(!is.na(head_depth),length_as=="total length"),
         aes(x=body_depth/precaudal_length,y=head_depth/head_length))+
    geom_point(aes(color=clade,shape=clade))+
    geom_smooth(formula=y~x,method="lm")+
    scale_x_continuous(labels=scales::label_percent(accuracy = 1))+
    scale_y_continuous(labels=scales::label_percent(accuracy = 1))+
    ggtitle("C")+
    theme(legend.position="none"),
  ggplot(data_final%>%filter(!is.na(head_depth),
                             !family %in% c("Balistidae","Monacanthidae"),
                             !order %in% c("Acanthuriformes"),
                             genus!="Selene",
                             length_as=="total length"),
         aes(x=body_depth/total_length,y=head_depth/OOL))+
    geom_point(aes(color=clade,shape=clade))+
    ggtitle("D")+
    geom_smooth(formula=y~x,method="lm")+
    scale_x_continuous(labels=scales::label_percent(accuracy = 1))+
    scale_y_continuous(labels=scales::label_percent(accuracy = 1))+
    theme(legend.position=c(0.8,0.2))+
    labs(color="Clade",shape="Clade")
  )
Plot of relative head and body proportions in fishes, showing how the length-height ratio of the head and body are closely correlated with one another. This is the case regardless of whether (A) the proportion between head length and height is compared to the proportion between total length and body height, (B) head length and total length are scaled to greatest body height, or (C) head length and height are compared to the proportion between precaudal/standard length and body depth rather than total length. (D) shows this correlated remains even when considering OOL as a proxy for head length, with the exception of Acanthuriformes, Balistoidei, and Selene vomer (omitted for clarity in D).

Figure 3.1: Plot of relative head and body proportions in fishes, showing how the length-height ratio of the head and body are closely correlated with one another. This is the case regardless of whether (A) the proportion between head length and height is compared to the proportion between total length and body height, (B) head length and total length are scaled to greatest body height, or (C) head length and height are compared to the proportion between precaudal/standard length and body depth rather than total length. (D) shows this correlated remains even when considering OOL as a proxy for head length, with the exception of Acanthuriformes, Balistoidei, and Selene vomer (omitted for clarity in D).

Note that the values measured on the axes in these graphs are showing the correlation between the proportional measurements between the aspect ratio of the head and body of a fish, not their absolute values. Thus, as fishes’ bodies get longer, their heads also get longer.

rbind(
  "head height/head length and body depth/total length "=summary(lm(I(body_depth/total_length)~I(head_depth/head_length),data_final))$r.squared,
  "body depth/head length and body depth/total length"=summary(lm(I(body_depth/total_length)~I(head_depth/total_length),data_final))$r.squared,
  "height height/head length and body depth/precaudal length"=summary(lm(I(body_depth/precaudal_length)~I(head_depth/head_length),data_final))$r.squared,
  "height height/OOL and body depth/total length"=summary(lm(I(body_depth/total_length)~I(head_depth/OOL),data_final))$r.squared
)%>%
  kable(digits=3,col.names="R2",caption="R<sup>2</sup> values for the correlation between relative proportions of head and body, using different criteria to define this proportion")%>%
  kable_styling()
Table 3.1: R2 values for the correlation between relative proportions of head and body, using different criteria to define this proportion
R2
head height/head length and body depth/total length 0.798
body depth/head length and body depth/total length 0.882
height height/head length and body depth/precaudal length 0.696
height height/OOL and body depth/total length 0.672

Based on this, it seems very clear that the proportions of the head and body in fishes are strongly correlated, with elongation of the head closely correlating to elongation of the body whether head elongation is defined relative to head height or body height, and whether body length is defined as precaudal length or total length.

4 Crude total length estimate using head length in Dunkleosteus terrelli

head_length_distribution<-
  data_final%>%filter(!is.na(total_length))%>%
  filter(!genus %in% c("Plesiobatis","Apletodon"),
         length_as=="total length")%>%
  drop_na(head_length)%>%
  group_by(taxon)%>%
  summarise(head_length=mean(head_length),total_length=mean(total_length))%>%
  mutate(percent_head_length=head_length/total_length)

mean_head_length<-mean(head_length_distribution$percent_head_length)
lower_head_length<-mean(head_length_distribution$percent_head_length)-(2*sd(head_length_distribution$percent_head_length))
upper_head_length<-mean(head_length_distribution$percent_head_length)+(2*sd(head_length_distribution$percent_head_length))
data.frame("Mean"=mean_head_length*100,
           "Lower C.I."=lower_head_length*100,
           "Upper C.I."=upper_head_length*100)%>%
  kable(digits=2,caption="Mean percent head length for all fishes, as well as 95% prediction interval for this proportion",
        col.names=c("Mean","Lower 95% P.I.","Lower 95% P.I."))%>%
  kable_styling()
Table 4.1: Mean percent head length for all fishes, as well as 95% prediction interval for this proportion
Mean Lower 95% P.I. Lower 95% P.I.
22.69 14.06 31.33
ggplot(head_length_distribution,aes(percent_head_length))+
  geom_histogram(col="black",fill="gray",bins=30)+
  geom_vline(xintercept=mean_head_length,
             linetype="dashed")+
  geom_vline(xintercept=lower_head_length,
             linetype="dotted",alpha=0.5)+
  geom_vline(xintercept=upper_head_length,
             linetype="dotted",alpha=0.5)+
  labs(x="Percent Head Length",y="Number of Species")+
  scale_x_continuous(labels=scales::label_percent(accuracy = 1))+
  theme_classic()
Histogram of percent head length in fishes, using species averages of adult and subadult individuals in the present dataset. Dashed line represents mean percent head length and dotted lines represent percent head lengths at +/- two standard deviations of the mean value.

Figure 4.1: Histogram of percent head length in fishes, using species averages of adult and subadult individuals in the present dataset. Dashed line represents mean percent head length and dotted lines represent percent head lengths at +/- two standard deviations of the mean value.

From this it is possible to determine that in fishes the total head length is, on average, about 22.7% of the total length of the organism.

fossil_taxa%>%
  filter(specimen %in% c("CMNH 7424","CMNH 6090","CMNH 7054","CMNH 5768"))%>%
  mutate(crude_total_length=head_length/mean_head_length,
         crude_total_length_lower=head_length/upper_head_length,
         crude_total_length_upper=head_length/lower_head_length)%>%
# The two are reversed here because a smaller % head length results in a longer body
  arrange(head_length)%>%
  rownames_to_column()%>%
  select(specimen,head_length,crude_total_length,
         crude_total_length_lower,crude_total_length_upper)%>%
  kable(digits=1,align=c("l","c","c","c"),
        col.names = c("Specimen","Head Length","Est. Length","Lower","Upper"),
        caption="Estimate of total length in <i>Dunkleosteus terrelli</i> using head length")%>%
  kable_styling()
Table 4.2: Estimate of total length in Dunkleosteus terrelli using head length
Specimen Head Length Est. Length Lower Upper
CMNH 7424 34.0 149.8 108.5 241.9
CMNH 6090 51.0 224.8 162.8 362.8
CMNH 7054 53.6 236.3 171.1 381.4
CMNH 5768 61.3 270.2 195.7 436.1

From this, it is possible to produce a very crude estimate suggesting that adult individuals of D. terrelli (i.e., CMNH 5768) have an estimated length of 2.7 m, which based on the proportions seen in extant fishes suggest a potential range of body length from 1.96-4.36 m.

fossil_taxa%>%
  filter(length_as!="estimated t.l.",clade=="Placodermi")%>%
  mutate(crude_total_length=head_length/mean_head_length,
         crude_total_length_lower=head_length/upper_head_length,
         crude_total_length_upper=head_length/lower_head_length,
         PE=(((crude_total_length-total_length)/crude_total_length))*100,
         taxon=str_replace(taxon,"_"," "))%>%
  rownames_to_column()%>%
  # The two are reversed here because a smaller % head length results in a longer body
  arrange(head_length)%>%
  select(taxon,specimen,head_length,crude_total_length,
         crude_total_length_lower,crude_total_length_upper,total_length,PE)%>%
  kable(digits=1,align=c("l","c","c","c","c","c","c","c"),
        caption="Estimate of total length in specimens of complete placoderms using head length",
        col.names = c("Taxon","Specimen","Head Length","Est. Length","Lower","Upper","Actual Length","%PE"))%>%
  column_spec(1, italic = T)%>%
  column_spec(c(4,7), bold = T)%>%
  kable_styling()
Table 4.3: Estimate of total length in specimens of complete placoderms using head length
Taxon Specimen Head Length Est. Length Lower Upper Actual Length %PE
Millerosteus minor FMNH PF 1089 2.3 10.3 7.5 16.6 13.7 -33.0
Millerosteus minor Composite Millerosteus 3.0 13.0 9.4 21.1 14.9 -14.6
Africanaspis dorissa Gess and Trinajstic 2017 4.6 20.2 14.6 32.6 23.0 -13.9
Incisoscutum ritchei Recon. (Trinajstic 2013) 5.7 25.3 18.3 40.8 30.3 -19.7
Coccosteus cuspidatus NMS 1893.107.27 6.2 27.5 19.9 44.4 29.6 -7.7
Coccosteus cuspidatus NMS 1897.55.6 6.5 28.5 20.7 46.0 32.3 -13.2
Coccosteus cuspidatus FMNH PF 1673 6.8 29.7 21.5 48.0 37.1 -24.8
Coccosteus cuspidatus Recon. (M & W 1968) 7.1 31.2 22.6 50.4 39.4 -26.3
Coccosteus cuspidatus NMS 1900.12.12 7.1 31.4 22.7 50.7 34.4 -9.6
Coccosteus cuspidatus ROM VP 52664 7.7 34.0 24.6 54.9 37.5 -10.3
Plourdosteus canadensis MNHM 2-177 8.7 38.4 27.8 62.0 37.5 2.3
Dickosteus threiplandi NMS 1987.7.118 9.2 40.6 29.4 65.6 43.7 -7.5
Holonema westolli Recon. (Miles 1971) 9.6 42.5 30.8 68.6 60.6 -42.7
Watsonosteus fletti NMS G.1995.4.2 11.5 50.7 36.7 81.9 56.6 -11.6
Gen. et sp. nov. CMNH 50233 12.4 54.5 39.5 88.0 63.0 -15.6
Dickosteus threiplandi NHMUK PV P 49663 12.7 55.7 40.4 90.0 52.3 6.2
Amazichthys trinajsticae AA.MEM.DS.8 13.6 59.8 43.4 96.6 89.7 -49.9

However, in complete specimens of arthrodires, using head length to estimate total length produces systematic underestimates of body length, which are quite substantial in some cases.

The 95% prediction interval for head length cited in Table 4.1 completely excludes the larger body sizes for Dunkleosteus typically cited in previous studies, which are almost invariable greater than 5 m in length for adult individuals like CMNH 5768. Even though head length produces underestimates of body size in arthrodires, there is essentially no way to produce sizes larger than 5 m even assuming head-body proportions comparable to Amazichthys (which produces an estimated length of 4.05 m for CMNH 5768).

Notably, the above results make absolutely no additional assumptions as to any other biological features of the taxa being considered, such as the shorter snouts of arthrodires compared to other fishes, the fact that the most extreme head proportions in this dataset are seen in strongly anguilliform or compressed body shapes that are not likely to be present in Dunkleosteus, etc. Thus, these estimates can be further refined, as seen below…

5 All taxon model

5.1 Graph of OOL against total length

fit.OOL<-lm(log(total_length)~log(OOL),
                            data_final %>% filter(length_as!="estimated t.l."))
(dunk_length_specimens<-ggplot(
  data_final%>%
    filter(length_as=="total length")%>%
    drop_na(OOL,total_length)%>%
    augment(fit.OOL,newdata=.,interval="prediction")%>%
    mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.OOL)$CF))%>%
    arrange(clade),
  aes(y=total_length,x=OOL))+
  geom_smooth(aes(color=clade),se=F,method="lm",formula=y~x)+
  geom_star(aes(fill=clade,starshape=clade),size=2)+
  geom_smooth(method="lm",formula=y~x)+
  geom_line(aes(y=.lower), color = "blue", linetype = "dashed")+
  geom_line(aes(y=.upper), color = "blue", linetype = "dashed")+
  geom_smooth(data=.%>%filter(clade=="Chondrichthyes"),
              method="lm",col="green",formula=y~x)+
  geom_star(aes(fill=clade,starshape=clade),color="white",starstroke=0.33,
            .%>%filter(clade=="Placodermi"),size=3,show.legend=F)+
  scale_starshape_manual(values=c(15,13,11,1,28))+
  scale_fill_manual(values=c(hue_pal()(4)[1:3],"black",hue_pal()(4)[4]),na.value=NA)+
  scale_color_manual(values=c(hue_pal()(4)[1:2],NA,NA,"NA"),na.value=NA)+
  labs(x="Orbit-Opercular Length (cm)",y="Total Length (cm)",
       color="Clade",starshape="Clade",fill="Clade")+
  scale_x_continuous(trans='log10')+
  scale_y_continuous(trans='log10')+
  geom_text(aes(x = 0.18, y = 1900, label = lm_eqn.all(fit.OOL)),
            hjust=0, parse = TRUE,data.frame())+
  theme_classic()+
  theme(legend.position=c(0.85,0.2)))
Plot of orbit-opercular length against total length in fishes (using a log10 scale), using individual specimen values.

Figure 5.1: Plot of orbit-opercular length against total length in fishes (using a log10 scale), using individual specimen values.

ggsave("Figure 7 (OOL Regression).tiff",dunk_length_specimens,device="tiff",
       width=170,height=127.5,dpi=600,units="mm",compression="lzw")

5.2 Results of model

OOL_residuals<-data_final%>%
  filter(!is.na(OOL),!is.na(total_length),length_as!="estimated t.l.")%>%
  mutate(residuals=(residuals(lm(log(total_length)~log(OOL)))))
summary(fit.OOL)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL), data = data_final %>% 
##     filter(length_as != "estimated t.l."))
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.61436 -0.14237  0.00201  0.13581  1.55312 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 1.900761   0.009076   209.4   <2e-16 ***
## log(OOL)    0.996190   0.004175   238.6   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2248 on 3167 degrees of freedom
##   (14 observations deleted due to missingness)
## Multiple R-squared:  0.9473, Adjusted R-squared:  0.9473 
## F-statistic: 5.693e+04 on 1 and 3167 DF,  p-value: < 2.2e-16
regression.stats(fit.OOL)

5.3 Diagnostic plots for model

par(mfrow=c(2,2))
plot(fit.OOL)
Diagnostic plots for model regressing total length against orbit-opercular length

Figure 5.2: Diagnostic plots for model regressing total length against orbit-opercular length

5.4 Comparing to untransformed model

5.4.1 Model summary

lm(total_length~OOL,data_final) %>%
  summary()
## 
## Call:
## lm(formula = total_length ~ OOL, data = data_final)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -198.50   -9.41   -4.55    4.54  362.53 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  8.90954    0.53010   16.81   <2e-16 ***
## OOL          5.86007    0.02866  204.48   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 23.64 on 3167 degrees of freedom
##   (53 observations deleted due to missingness)
## Multiple R-squared:  0.9296, Adjusted R-squared:  0.9296 
## F-statistic: 4.181e+04 on 1 and 3167 DF,  p-value: < 2.2e-16

5.4.2 Graph of untransformed OOL versus total length

ggplot(
  data_final%>%
    filter(length_as=="total length")%>%
    drop_na(OOL,total_length)%>%
    augment(lm(total_length~OOL,data_final),newdata=.,interval="prediction")%>%
    arrange(clade),
  aes(y=total_length,x=OOL))+
  geom_smooth(aes(color=clade),se=F,method="lm",formula=y~x)+
  geom_star(aes(fill=clade,starshape=clade),size=2)+
  geom_smooth(method="lm",formula=y~x)+
  geom_line(aes(y=.lower), color = "blue", linetype = "dashed")+
  geom_line(aes(y=.upper), color = "blue", linetype = "dashed")+
  geom_smooth(data=.%>%filter(clade=="Chondrichthyes"),
              method="lm",col="green",formula=y~x)+
  geom_star(aes(fill=clade,starshape=clade),color="white",starstroke=0.33,
            .%>%filter(clade=="Placodermi"),size=3,show.legend=F)+
  scale_starshape_manual(values=c(15,13,11,1,28))+
  scale_fill_manual(values=c(hue_pal()(4)[1:3],"black",hue_pal()(4)[4]),na.value=NA)+
  scale_color_manual(values=c(hue_pal()(4)[1:2],NA,NA,"NA"),na.value=NA)+
  labs(x="Orbit-Opercular Length (cm)",y="Total Length (cm)",
       color="Clade",starshape="Clade",fill="Clade")+
  geom_text(aes(x = 0.18, y = 950,
                label = lm_eqn.all(lm(total_length~OOL,data_final))),
            hjust=0, parse = TRUE,data.frame())+
  theme_classic()+
  coord_cartesian(y=c(-50,1000))+
  theme(legend.position=c(0.85,0.2))
Plot of OOL versus total length using untransformed data. Note that the regression line is calculated on the data without log-transformation, this is not the regression line of the log-transformed data back-transformed to arithmetic units.

Figure 5.3: Plot of OOL versus total length using untransformed data. Note that the regression line is calculated on the data without log-transformation, this is not the regression line of the log-transformed data back-transformed to arithmetic units.

5.4.3 Diagnostic plots

par(mfrow=c(2,2))
plot(lm(total_length~OOL,data_final))
Diagnostic plots for untransformed model

Figure 5.4: Diagnostic plots for untransformed model

5.4.4 Histogram of residuals

grid.arrange(ncol=2,
 ggplot(data_final%>%filter(!is.na(OOL))%>%
          filter(!is.na(total_length)),
        aes(x=residuals(lm(total_length~OOL))))+
   geom_histogram(col="black",fill="gray",bins=30)+
   stat_function(fun = function(x) dnorm(x,
                                         mean=mean(residuals(lm(total_length~OOL,data_final),na.rm=TRUE)),
                                         sd=sd(residuals(lm(total_length~OOL,data_final),na.rm=TRUE)))*
                        nrow(data.frame(residuals(lm(total_length~OOL,data_final),na.rm=TRUE)))*
                        ((max(residuals(lm(total_length~OOL,
                                           data_final)))+
                           abs(min(residuals(lm(total_length~OOL,
                                                data_final)))))/
                           30),
                color = "red", size = 0.5)+
   ggtitle("Untransformed Model")+
   labs(y="Count",x="Residuals")+theme_classic(),
 ggplot(data_final%>%filter(!is.na(OOL))%>%
          filter(!is.na(total_length)),
        aes(x=residuals(lm(log(total_length)~log(OOL)))))+
   geom_histogram(col="black",fill="gray",bins=30)+
   stat_function(fun = function(x) dnorm(x,
                                         mean=mean(residuals(lm(log(total_length)~log(OOL),data_final),na.rm=TRUE)),
                                         sd=sd(residuals(lm(log(total_length)~log(OOL),data_final),na.rm=TRUE)))*
                        nrow(data.frame(residuals(lm(log(total_length)~log(OOL),data_final),na.rm=TRUE)))*
                        ((max(residuals(lm(log(total_length)~
                                             log(OOL),
                                           data_final)))+
                           abs(min(residuals(lm(log(total_length)~
                                                  log(OOL),
                                                data_final)))))/
                           30),
                color = "red", size = 0.5)+
   ggtitle("Log-Transformed Model")+
   labs(y="Count",x="Residuals")+theme_classic()
)
Histogram of residuals in untransformed and natural log transformed model

Figure 5.5: Histogram of residuals in untransformed and natural log transformed model

Based on the heterosketasticity in the scale-location plot, increased leverage of certain highly influential points (mostly large taxa), and the strongly leptokurtic distribution of the residuals of the untransformed model, a model in which the data is log-transformed before analysis is preferred here.

5.4.5 Examination of kurtosis

rbind("Untransformed Model"=data_final%$%
  lm(total_length~OOL)%>%
  residuals()%>%
  kurtosis(),
"Log-Transformed Model"=fit.OOL%>%
  residuals()%>%
  kurtosis(),
"Back-Transformed Residuals"=fit.OOL%>%
  residuals()%>%
  exp()%>%
  kurtosis()
)%>%data.frame()%>%
  add_column(skewness=c(skewness(residuals(lm(total_length~OOL,data_final))),
                        skewness(residuals(fit.OOL)),
                        skewness(exp(residuals(fit.OOL)))))%>%
  kable(digits=2,align="c",col.names=c("Excess Kurtosis","Skewness"),
          caption="Kurtosis and skewness for the regression model between OOL with and without log-transformation, as well as the residuals from a log-transformed model back-transformed to an arithmetic scale.")%>%
  kable_styling()
Table 5.1: Kurtosis and skewness for the regression model between OOL with and without log-transformation, as well as the residuals from a log-transformed model back-transformed to an arithmetic scale.
Excess Kurtosis Skewness
Untransformed Model 37.81 2.77
Log-Transformed Model 2.20 0.44
Back-Transformed Residuals 32.73 3.18

Based on this, the log-transformed model shows minor excess kurtosis (kurtosus < 3), and little skewness. However, when back-transformed into an arithmetic scale, the residuals of the model show extreme kurtosis (> 30). This means that while log-transformation normalizes the distribution of data and makes it possible to accurately fit a regression model, when back-transformed into arithmetic terms it fails to accurately project prediction intervals for new data, since the prediction intervals are based in part on the standard error of the model (and hence the residuals).

Or, to put it in less statistically minded terms, de-transforming a log-log model results outliers being exaggerated, because error goes from being calculated on a log-scale to an unlogged one. This results in outliers having a disproportionate effect on the model. Detransformation will transform almost any study’s distribution of residuals from a nearly-normal distribution to a leptokurtic one, unless the model itself is almost perfectly normal (which it never is). Calculating prediction intervals on leptokurtic models is known to drastically inflate error bounds (Miller, 1986), however there are few suggestions to how to apply this to log-transformed regression equations, such as those of most biological data.

5.4.6 Estimates of placoderms using an untransformed model

fossil_taxa%>%
  filter(!!fossil.specimens)%>%
  augment(lm(total_length~OOL,data_final),newdata=.,interval="predict")%>%
  select(taxon,specimen,total_length,.fitted,.lower,.upper)%>%
  kable(digits=1,align=c("l","c","c","c","c","c"),
        col.names = c("Taxon","Specimen","Actual Length","Est.","Lower 95% P.I.","Upper 95% P.I."),
        caption="Length estimates for placoderms known from complete body fossils using a non-log transformed model.")%>%
  column_spec(1,italic=T)%>%
  add_header_above(c(" "=3,"Estimated Length"=3))%>%
  column_spec(4,bold=T)%>%
  kable_styling()
Table 5.2: Length estimates for placoderms known from complete body fossils using a non-log transformed model.
Estimated Length
Taxon Specimen Actual Length Est. Lower 95% P.I. Upper 95% P.I.
Millerosteus minor FMNH PF 1089 13.7 20.9 -25.5 67.2
Millerosteus minor Composite Millerosteus 14.9 22.7 -23.6 69.1
Africanaspis dorissa Gess and Trinajstic 2017 23.0 30.0 -16.3 76.4
Incisoscutum ritchei Recon. (Trinajstic 2013) 30.3 36.2 -10.1 82.6
Coccosteus cuspidatus NMS 1893.107.27 29.6 39.3 -7.1 85.6
Coccosteus cuspidatus NMS 1897.55.6 32.3 40.1 -6.3 86.4
Coccosteus cuspidatus FMNH PF 1673 37.1 40.5 -5.9 86.9
Coccosteus cuspidatus Recon. (M & W 1968) 39.4 46.9 0.6 93.3
Coccosteus cuspidatus NMS 1900.12.12 34.4 46.2 -0.1 92.6
Coccosteus cuspidatus ROM VP 52664 37.5 45.7 -0.6 92.1
Plourdosteus canadensis MNHM 2-177 37.5 53.4 7.1 99.8
Dickosteus threiplandi NMS 1987.7.118 43.7 57.5 11.2 103.9
Holonema westolli Recon. (Miles 1971) 60.6 53.2 6.9 99.6
Watsonosteus fletti NMS G.1995.4.2 56.6 65.5 19.2 111.9
Gen. et sp. nov. CMNH 50233 63.0 68.3 21.9 114.7
Dickosteus threiplandi NHMUK PV P 49663 52.3 69.2 22.8 115.5
Amazichthys trinajsticae AA.MEM.DS.8 89.7 76.6 30.2 123.0
Dunkleosteus terrelli CMNH 7424 NA 178.9 132.5 225.2
Dunkleosteus terrelli CMNH 6090 NA 264.4 218.0 310.8
Dunkleosteus terrelli CMNH 7054 NA 275.5 229.1 321.9
Dunkleosteus terrelli CMNH 5768 NA 316.6 270.1 363.0

Note how for the very smallest placoderm taxa (e.g., Millerosteus, Coccosteus) the 95% prediction intervals for the model where the data is not log-transformed before regressing are negative. This is likely a result of heteroskedasticity in the model (i.e., larger fishes are measured with more error), which means that an untransformed model is unsuitable for estimating length in Dunkleosteus.

5.5 Distribution of residuals

ggplot(OOL_residuals%>%filter(length_as!="estimated t.l."),
       aes(y=residuals,x=clade))+
  geom_violin(aes(fill=clade),scale="width")+
  geom_hline(yintercept=0,linetype="dashed")+
  geom_hline(data=.%>%filter(clade=="Placodermi"),
             aes(yintercept=mean(residuals)),color="#00B0F6",linetype="dashed")+
  geom_boxplot(width=0.3)+
  scale_y_continuous(breaks=seq(-1.5,1.5,0.2))+
  labs(y="Residuals",x="Clade")+
  theme(legend.position="none")
Box-and-whisker plot of distribution of residuals in OOL model separated by clade.

Figure 5.6: Box-and-whisker plot of distribution of residuals in OOL model separated by clade.

As can be seen, arthrodire placoderms show slightly negative residuals (i.e., a slight overestimate of body length), but overall their residuals are well within the range of variation of other fishes.

5.6 Species-average model

fit.species_average<-lm(log(total_length)~log(OOL),
                              data_species%>%
                           column_to_rownames("taxon"))
fit.species_average2<-lm(log(total_length)~log(OOL),
                              data_species%>%
                                filter(!shape %in% 
                                         c("macruriform","compressiform","anguiliform")%>%
                                         replace_na(TRUE),
                                       !family %in% c("Balistidae","Monacanthidae",
                                                      "Serranidae","Holocentridae"),
                                       !genus %in% c("Rhinochimaera"))%>%
                                column_to_rownames("taxon"))
data_species<-data_species%>%ungroup()%>%
  mutate(fit=exp(predict(fit.species_average,data_species,interval="prediction"))[,1],
         lwr=exp(predict(fit.species_average,data_species,interval="prediction"))[,2],
         upr=exp(predict(fit.species_average,data_species,interval="prediction"))[,3],
         residuals=residuals(fit.species_average2)[match(data_species$taxon,names(fit.species_average2$residuals))])
ggplot(
  data_species%>%
    drop_na(OOL,total_length)%>%
    augment(fit.species_average,newdata=.,interval="prediction")%>%
    mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.species_average)$CF))%>%
    arrange(clade),
  aes(y=total_length,x=OOL))+
  geom_smooth(aes(color=clade),se=F,method="lm",formula=y~x)+
  geom_star(aes(fill=clade,starshape=clade),size=2)+
  geom_smooth(method="lm",formula=y~x)+
  geom_line(aes(y=.lower), color = "blue", linetype = "dashed")+
  geom_line(aes(y=.upper), color = "blue", linetype = "dashed")+
  geom_smooth(data=.%>%filter(clade=="Chondrichthyes"),
              method="lm",col="green",formula=y~x)+
  geom_star(aes(fill=clade,starshape=clade),color="white",starstroke=0.33,
            .%>%filter(clade=="Placodermi"),size=3,show.legend=F)+
  scale_starshape_manual(values=c(15,13,11,1,28))+
  scale_fill_manual(values=c(hue_pal()(4)[1:3],"black",hue_pal()(4)[4]),na.value=NA)+
  scale_color_manual(values=c(hue_pal()(4)[1:2],NA,NA,"NA"),na.value=NA)+
  labs(x="Orbit-Opercular Length (cm)",y="Total Length (cm)",
       color="Clade",starshape="Clade",fill="Clade")+
  scale_x_continuous(trans='log10')+
  scale_y_continuous(trans='log10')+
  geom_text(aes(x = 0.18, y = 1900, label = lm_eqn.all(fit.species_average)),
            hjust=0, parse = TRUE,data.frame())+
  theme_classic()+
  theme(legend.position=c(0.85,0.2))
Plot of orbit-opercular length against total length in fishes (on a log10 scale), using the average value for adults of each species.

Figure 5.7: Plot of orbit-opercular length against total length in fishes (on a log10 scale), using the average value for adults of each species.

write.csv(data_species%>%select(taxon,clade,N,OOL,total_length,everything()),
          file="ool_data_cleaned_speciesaverages.csv")
summary(fit.species_average)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL), data = data_species %>% 
##     column_to_rownames("taxon"))
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.56385 -0.13633 -0.01091  0.11679  1.58038 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 1.911462   0.016657   114.8   <2e-16 ***
## log(OOL)    0.982030   0.008339   117.8   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2334 on 965 degrees of freedom
##   (4 observations deleted due to missingness)
## Multiple R-squared:  0.9349, Adjusted R-squared:  0.9349 
## F-statistic: 1.387e+04 on 1 and 965 DF,  p-value: < 2.2e-16
regression.stats(fit.species_average)

5.6.1 Species averages, excluding anguilliform, compressiform, and macruriform taxa

summary(fit.species_average2)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL), data = data_species %>% 
##     filter(!shape %in% c("macruriform", "compressiform", "anguiliform") %>% 
##         replace_na(TRUE), !family %in% c("Balistidae", "Monacanthidae", 
##         "Serranidae", "Holocentridae"), !genus %in% c("Rhinochimaera")) %>% 
##     column_to_rownames("taxon"))
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.50908 -0.11894 -0.01996  0.09556  1.55340 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 1.951446   0.015773   123.7   <2e-16 ***
## log(OOL)    0.977179   0.007704   126.8   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2034 on 775 degrees of freedom
##   (4 observations deleted due to missingness)
## Multiple R-squared:  0.954,  Adjusted R-squared:  0.954 
## F-statistic: 1.609e+04 on 1 and 775 DF,  p-value: < 2.2e-16
regression.stats(fit.species_average2)

5.7 Percent of taxa that have estimates falling withing X percent of their actual length

rbind(data_final%>%
  mutate(fit=exp(predict(fit.OOL,.))*regression.stats(fit.OOL)$CF,
         PE=(fit-total_length)/fit)%>%
  mutate(onethird=ifelse(PE<0.333&PE>(-0.333),1,0),
         onefifth=ifelse(PE<0.2&PE>(-0.2),1,0))%>%
  drop_na(onethird,onefifth)%>%
  select(onethird,onefifth)%>%
  summarise(across(onethird:onefifth,~mean(.))),
      data_species%>%
  mutate(fit=exp(predict(fit.species_average,.))*regression.stats(fit.species_average)$CF,
         PE=(fit-total_length)/fit)%>%
  mutate(onethird=ifelse(PE<0.333&PE>(-0.333),1,0),
         onefifth=ifelse(PE<0.2&PE>(-0.2),1,0))%>%
  drop_na(onethird,onefifth)%>%
  select(onethird,onefifth)%>%
  summarise(across(onethird:onefifth,~mean(.)))
)%>%
  mutate(across(everything(),~.*100),
         rowname=c("Individual Specimens","Species Averages"))%>%
  column_to_rownames("rowname")%>%
  kable(digits=2,align=c("l","c","c"),
        col.names = c("+/- 33%","+/- 20%"))%>%
  add_header_above(c(" "=1,"Percent of observations within X percent of actual value"=2))%>%
  kable_styling()
Percent of observations within X percent of actual value
+/- 33% +/- 20%
Individual Specimens 88.29 67.31
Species Averages 87.80 68.56

Based on this, estimated lengths for the majority of taxa fall within +/- 20% of the actual value, and nearly all taxa fall within +/- 33% of the actual value. The taxa that do not tend to be those with extreme body plans (e.g., hyper-anguilliform, hyper-compressiform, extremely posteriorly shifted orbit), which suggests that the returned 95% prediction intervals for the model (which tend to be +/- 50% or more estimated length) are not accurate and using +/- %PE is a more accurate way of expressing uncertainty in length estimates for arthrodires.

5.8 Distribution of head proportions across clades

grid.arrange(
ggplot(data_final%>%filter(length_as!="estimated t.l.",!is.na(total_length),!is.na(head_length))%>%
         group_by(taxon)%>%
         summarise(total_length=mean(total_length),head_length=mean(head_length),clade=unique(clade)),
       aes(y=head_length/total_length,x=clade))+
  geom_hline(aes(yintercept=mean(head_length/total_length)))+
  geom_hline(data=.%>%filter(clade=="Placodermi"),color=hue_pal()(5)[4],
             aes(yintercept=mean(head_length/total_length)))+
  geom_violin(aes(fill=clade),scale="width")+
  geom_boxplot(width=0.3)+
  labs(x="Clade",y="Percent of Total Length")+
  scale_y_continuous(labels=scales::label_percent(accuracy = 1))+
  ggtitle("Head Length as Percent of Total Length")+
  theme(legend.position="none"),
ggplot(data_final%>%filter(length_as!="estimated t.l.",!is.na(total_length),!is.na(OOL))%>%
         group_by(taxon)%>%
         summarise(snout_length=mean(snout_length),total_length=mean(total_length),OOL=mean(OOL),clade=unique(clade)),
       aes(y=OOL/total_length,x=clade))+
  geom_hline(aes(yintercept=mean(OOL/total_length)))+
  geom_hline(data=.%>%filter(clade=="Placodermi"),color=hue_pal()(5)[4],
             aes(yintercept=mean(OOL/total_length)))+
  geom_violin(aes(fill=clade),scale="width")+
  geom_boxplot(width=0.3)+
  labs(x="Clade",y="Percent of Total Length")+
  ggtitle("OOL as Percent of Total Length")+
  scale_y_continuous(labels=scales::label_percent(accuracy = 1))+
  theme(legend.position="none"),
ggplot(data_final%>%filter(length_as!="estimated t.l.",!is.na(total_length),!is.na(OOL),!is.na(snout_length))%>%
         group_by(taxon)%>%
         summarise(snout_length=mean(snout_length),total_length=mean(total_length),OOL=mean(OOL),clade=unique(clade)),
       aes(y=OOL/(total_length-snout_length),x=clade))+
  geom_hline(aes(yintercept=mean(OOL/(total_length-snout_length))))+
  geom_hline(data=.%>%filter(clade=="Placodermi"),color=hue_pal()(5)[4],
             aes(yintercept=mean(OOL/(total_length-snout_length))))+
  geom_violin(aes(fill=clade),scale="width")+
  geom_boxplot(width=0.3)+
  labs(x="Clade",y="Percent of Total Length")+
  scale_y_continuous(labels=scales::label_percent(accuracy = 1))+
  ggtitle("OOL as Percent of Total Length minus Snout Length")+
  theme(legend.position="none"))
Distributions of head length as a percent of total length (top), OOL as a percent of total length (middle), and OOL as a percent of total length minus snout length (bottom) in fishes examined in this study. Data points for these box-and-whisker plots are species-averages.

Figure 5.8: Distributions of head length as a percent of total length (top), OOL as a percent of total length (middle), and OOL as a percent of total length minus snout length (bottom) in fishes examined in this study. Data points for these box-and-whisker plots are species-averages.

Overall, placoderms seem to show heads that are comparable in relative length to other fishes. OOL as percent total length is slightly high (though still within the range of variation), but this appears to be due to the unusually short snout of arthrodires relative to other gnathostomes.

5.9 Testing to see if different clades have different regression lines

summary(lm(log(total_length)~log(OOL)+clade,
           data_final))
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) + clade, data = data_final)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.59829 -0.12924 -0.00425  0.10517  1.57293 
## 
## Coefficients:
##                          Estimate Std. Error t value Pr(>|t|)    
## (Intercept)              1.884758   0.009115 206.778   <2e-16 ***
## log(OOL)                 0.994769   0.004481 221.997   <2e-16 ***
## cladeChondrichthyes      0.015222   0.011311   1.346    0.178    
## cladePetromyzontiformes  0.146521   0.012561  11.665   <2e-16 ***
## cladePlacodermi         -0.061216   0.053592  -1.142    0.253    
## cladeSarcopterygii      -0.034912   0.055525  -0.629    0.530    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2201 on 3163 degrees of freedom
##   (53 observations deleted due to missingness)
## Multiple R-squared:  0.9495, Adjusted R-squared:  0.9495 
## F-statistic: 1.19e+04 on 5 and 3163 DF,  p-value: < 2.2e-16
options(width=1000)
summary(lm(log(total_length)~log(OOL)*clade,
           data_final))
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) * clade, data = data_final)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.59791 -0.11815 -0.00554  0.10533  1.55427 
## 
## Coefficients:
##                                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                       1.846948   0.009576 192.866   <2e-16 ***
## log(OOL)                          1.016435   0.004818 210.975   <2e-16 ***
## cladeChondrichthyes               0.377939   0.033054  11.434   <2e-16 ***
## cladePetromyzontiformes          -0.003554   0.086416  -0.041   0.9672    
## cladePlacodermi                   0.047962   0.203902   0.235   0.8141    
## cladeSarcopterygii                1.296739   1.057700   1.226   0.2203    
## log(OOL):cladeChondrichthyes     -0.139344   0.011991 -11.621   <2e-16 ***
## log(OOL):cladePetromyzontiformes  0.076935   0.044931   1.712   0.0869 .  
## log(OOL):cladePlacodermi         -0.061467   0.109895  -0.559   0.5760    
## log(OOL):cladeSarcopterygii      -0.449506   0.349320  -1.287   0.1983    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2155 on 3159 degrees of freedom
##   (53 observations deleted due to missingness)
## Multiple R-squared:  0.9517, Adjusted R-squared:  0.9516 
## F-statistic:  6916 on 9 and 3159 DF,  p-value: < 2.2e-16

Comparing regression lines with both shape and clade

options(width=1000)
summary(lm(log(total_length)~log(OOL)*clade+shape,
           data_final))
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) * clade + shape, data = data_final)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.63004 -0.09248  0.00986  0.09829  1.20185 
## 
## Coefficients:
##                                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                       1.851146   0.008399 220.404  < 2e-16 ***
## log(OOL)                          0.988477   0.004177 236.629  < 2e-16 ***
## cladeChondrichthyes               0.146592   0.028976   5.059 4.46e-07 ***
## cladePetromyzontiformes          -0.474570   0.075036  -6.325 2.90e-10 ***
## cladePlacodermi                   0.102825   0.171002   0.601  0.54768    
## cladeSarcopterygii                1.292541   0.886844   1.457  0.14509    
## shapeanguilliform                 0.466817   0.020294  23.003  < 2e-16 ***
## shapecompressiform               -0.096815   0.011969  -8.089 8.51e-16 ***
## shapeelongate                     0.240656   0.009920  24.261  < 2e-16 ***
## shapeflattened                    0.073593   0.036068   2.040  0.04140 *  
## shapemacruriform                  0.422009   0.029736  14.192  < 2e-16 ***
## log(OOL):cladeChondrichthyes     -0.055296   0.010514  -5.259 1.54e-07 ***
## log(OOL):cladePetromyzontiformes  0.104893   0.037688   2.783  0.00542 ** 
## log(OOL):cladePlacodermi         -0.074344   0.092147  -0.807  0.41984    
## log(OOL):cladeSarcopterygii      -0.421548   0.292893  -1.439  0.15018    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1807 on 3154 degrees of freedom
##   (53 observations deleted due to missingness)
## Multiple R-squared:  0.9661, Adjusted R-squared:  0.9659 
## F-statistic:  6420 on 14 and 3154 DF,  p-value: < 2.2e-16
anova(lm(log(total_length)~log(OOL)*clade+shape,
           data_final))

Comparing regression lines with both shape and clade, using species averages

options(width=1000)
data_species %>%
  lm(log(total_length)~log(OOL)*clade+shape,.)%>%
  summary()
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) * clade + shape, data = .)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.59859 -0.11207  0.00263  0.09726  1.15804 
## 
## Coefficients:
##                                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                       1.900713   0.016195 117.365  < 2e-16 ***
## log(OOL)                          0.960253   0.008935 107.467  < 2e-16 ***
## cladeChondrichthyes               0.099869   0.062688   1.593 0.111465    
## cladePetromyzontiformes          -0.626886   0.219484  -2.856 0.004381 ** 
## cladePlacodermi                   0.027436   0.243927   0.112 0.910469    
## cladeSarcopterygii                2.186985   2.236617   0.978 0.328417    
## shapeanguilliform                 0.491471   0.036919  13.312  < 2e-16 ***
## shapecompressiform               -0.070926   0.020863  -3.400 0.000703 ***
## shapeelongate                     0.199297   0.020550   9.698  < 2e-16 ***
## shapeflattened                    0.085480   0.055041   1.553 0.120751    
## shapemacruriform                  0.401588   0.049930   8.043 2.59e-15 ***
## log(OOL):cladeChondrichthyes     -0.023399   0.023541  -0.994 0.320490    
## log(OOL):cladePetromyzontiformes  0.159576   0.154635   1.032 0.302356    
## log(OOL):cladePlacodermi         -0.020417   0.126127  -0.162 0.871438    
## log(OOL):cladeSarcopterygii      -0.704677   0.763649  -0.923 0.356358    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1989 on 952 degrees of freedom
##   (4 observations deleted due to missingness)
## Multiple R-squared:  0.9534, Adjusted R-squared:  0.9527 
## F-statistic:  1390 on 14 and 952 DF,  p-value: < 2.2e-16

If examining based on species averages sharks and other fish groups do not have significant differences in slope between clades.

data_final%>%
  augment(fit.OOL,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.OOL)$CF))%>%
  drop_na(total_length,OOL)%>%
  ggplot(.,
       aes(y=total_length,x=OOL))+
  geom_point(aes(shape=clade),color="dark grey",fill="light grey",show.legend=F)+
  geom_smooth(method="lm",formula=y~x,alpha=0.25,se=F)+
  geom_point(data=.%>%filter(clade=="Chondrichthyes")%>%
               drop_na(shape)%>%
               mutate(shape=factor(ifelse(family %in%
                                            c("Lamnidae","Megachasmatidae","Echinorhinidae"),
                                          "Lam./Megac./Echin.",
                                          as.character(shape))),
                      shape=relevel(factor(shape),"fusiform")),
             aes(shape=clade,fill=shape))+
  geom_smooth(data=.%>%filter(clade=="Chondrichthyes"),color="black",
              method="lm",formula=y~x,alpha=0.25)+
  geom_line(aes(y=.lower), color = "#3366FF", linetype = "dashed",alpha=0.5)+
  geom_line(aes(y=.upper), color = "#3366FF", linetype = "dashed",alpha=0.5)+
  scale_shape_manual(values=c(21,24,22,23,25),guide="none")+
  scale_x_continuous(trans='log10')+
  scale_y_continuous(trans='log10')+
  labs(x="Orbit-Opercular Length (cm)",y="Total Length (cm)",fill="Body Shape")+
  theme_classic()+
  theme(legend.position=c(0.7,0.25))+
  guides(fill=guide_legend(override.aes = list(shape = 24)))
Plot of OOL against total length in fishes on a log10 scale, focusing on sharks, showing the non-random distribution of sharks with respect to size and body shape. The very smallest sharks (e.g., Scyliorhinidae) all have elongate bodies, whereas the very largest sharks tend to be Lamniformes (Lamnidae, Megachasmatidae) or Echinorhiniformes with very short trunk proportions (abbreviated as “Lam./Megac./Echin.” in the legend). Thus, although chondrichthyans show a different regresson linefrom other taxa, this is not due to a different allometric relationship between OOL and total length but rather due to non-random differences in ecology that are mirrored in other fishes (i.e., tunas also show shorter bodies than other fishes). Black line represents regression for chondrichthyans whereas blue line represents regression for all fishes.

Figure 5.9: Plot of OOL against total length in fishes on a log10 scale, focusing on sharks, showing the non-random distribution of sharks with respect to size and body shape. The very smallest sharks (e.g., Scyliorhinidae) all have elongate bodies, whereas the very largest sharks tend to be Lamniformes (Lamnidae, Megachasmatidae) or Echinorhiniformes with very short trunk proportions (abbreviated as “Lam./Megac./Echin.” in the legend). Thus, although chondrichthyans show a different regresson linefrom other taxa, this is not due to a different allometric relationship between OOL and total length but rather due to non-random differences in ecology that are mirrored in other fishes (i.e., tunas also show shorter bodies than other fishes). Black line represents regression for chondrichthyans whereas blue line represents regression for all fishes.

5.10 Effects of ontogeny on model error

5.10.1 Effects of ontogeny with for smaller actinopterygians

Note: these data use the initial data set that does not filter out the confirmed juvenile individuals prior to the analysis.

data_initial%>%
  drop_na(OOL,total_length)%>%
  filter(references!="Slagle pers. comm.")%>%
#Removed this one because I could only measure it from a photo at an angle, not personally, and it is much bigger than all other specimens, so I am not 100% sure if it is reliable
  augment(fit.OOL,newdata=.)%>%
  filter(taxon %in% c("Hiodon_tergisus",
                      "Amblopites_rupestris",
                      "Alosa_pseudoharengus",
                      "Neogobius_melanostomus",
                      "Etheostoma_caeruleum",
                      "Micropterus_dolomieu"))%>%
  ggplot(aes(total_length,.resid))+
  geom_point(aes(fill=genus),shape=21)+
  geom_smooth(aes(color=genus),formula=y~x,method="lm")+
  labs(x="Total Length (cm)",y="Residuals",fill="Species",shape="Species",color="Species")+
  theme(legend.text = element_text(face = "italic"))
Plot of residuals versus total length for smaller fishes that could be measured directly

Figure 5.10: Plot of residuals versus total length for smaller fishes that could be measured directly

5.10.2 Effects of ontogeny with for Geotria

data_initial%>%
  filter(taxon=="Geotria_macrostoma") %>%
  augment(fit.OOL,newdata=.)%>%
  ggplot(.,aes(total_length,.resid))+
  geom_point(fill="gray",shape=21)+
  geom_smooth(method="lm",formula=y~x)+
  labs(x="Total Length (cm)",y="Residuals")
Plot of residuals versus total length in the lamprey Geotria macrostoma using data from Baker et al. (2021).

Figure 5.11: Plot of residuals versus total length in the lamprey Geotria macrostoma using data from Baker et al. (2021).

There is a slightly positive allometry in the residuals for lampreys, though not as strong as for actinopterygians.

5.10.3 Effects of ontogeny with sharks and large, pelagic fishes

data_initial %>% 
  filter(taxon %in% c("Makaira_nigricans", "Kajikia_audax", "Kajikia_albida",
                      "Acanthocybium_solandri", "Thunnus_thynnus", "Carcharhinus_plumbeus",
                      "Carcharhinus_leucas", "Rhizoprionodon_acutus", "Echinorhinus_cookei")) %>%
  mutate(taxon=gsub("_"," ",taxon))%>%
  augment(fit.OOL,newdata=.)%>%
  drop_na(.resid,total_length)%>%
  ggplot(aes(total_length,.resid))+
  geom_point(aes(fill=taxon,shape=clade))+
  scale_shape_manual(values=c(21,24),guide="none")+
  scale_linetype(guide="none")+
  geom_smooth(aes(color=taxon,linetype=clade),method="lm",formula=y~x,se=F)+
  geom_point(aes(fill=taxon,shape=clade),
             .%>%filter(taxon=="Echinorhinus cookei",total_length<100))+
  labs(x="Total Length (cm)",y="Residuals",color="Species",fill="Species",shape="Clade",
       linetype="Clade")+
  theme(legend.text = element_text(face = "italic"))+
  guides(fill=guide_legend(override.aes = list(shape = c(21,24,24,24,21,21,21,24,21),
                                               linetype = c("solid","dotted","dotted","dotted","solid","solid","solid","dotted","solid"))))
Plot of residuals versus total length for larger fish species with large sample sizes. Chondrichthyans are represented by triangles and dashed lines whereas actinopterygians are represented by circles and solid lines.

Figure 5.12: Plot of residuals versus total length for larger fish species with large sample sizes. Chondrichthyans are represented by triangles and dashed lines whereas actinopterygians are represented by circles and solid lines.

As can be seen, there is a consistent negative allometry between OOL and total length in ray-finned fishes, and to a lesser degree lampreys. By contrast, no clear relationship is present in sharks.

5.10.4 Effects of ontogeny with for Eusthenopteron from Schultze (1984)

eusthenopteron<-data.frame(
  snout_length=c(1.5,1.2,NA,NA,2.0,2.8,2.8,2.7,2.7,2.6,NA,2.4,2.7,2.9,2.9,NA,NA,NA,NA,NA,4.4,3.3,
  3.3,3.6,NA,NA,NA,NA,NA,3.4,NA,NA,NA,NA,NA,11.1,NA,NA,NA,NA,NA,NA,NA,12,14.5,13.8,
  NA,NA,19.8,32,0),
  head_length=c(7.5,7.2,8.2,8.2,11.1,11.7,11.7,11.7,11.7,13.3,NA,12.6,14,14.2,14.6,
                16.2,19.6,16.7,20.9,20.1,19.5,NA,NA,22.1,25.3,26.1,NA,26.3,25.4,27.2,
                30.8,32.8,31.9,39.9,39.2,56.3,51.4,51.2,45.2,45.2,NA,53.3,54.9,64.6,
                72,76.5,63.9,81.1,87.5,135.5,191),
  precaudal_length=c(27.2,27.2,28.9,28.3,NA,44.3,44.3,50.8,50.8,45,45,53.7,NA,NA,
                     58.3,62.7,64,67.7,78.3,78.4,78.4,93.1,93.1,NA,100.4,115.6,NA,NA,
                     120.9,NA,NA,NA,133.7,168.3,168.3,200.4,200.8,210.3,218.9,218.9,
                     NA,248.2,252.6,262.1,317.3,342.3,371.9,376.4,NA,NA,793.9),
  total_length=c(28,28,31.8,32.2,NA,48,48,51.5,51.5,49.2,NA,58,NA,NA,NA,70.4,67.2,
               NA,86.4,84.3,83.1,96.5,96.5,NA,113.7,129.6,120.9,NA,NA,NA,NA,NA,
               NA,NA,NA,215.9,NA,NA,227,227,NA,NA,NA,NA,331.9,345.9,NA,NA,NA,NA,808.5)
)%>%
  mutate(OOL=head_length-snout_length) %>%
  augment(lm(total_length~precaudal_length,.),newdata=.)%>%
  mutate(est_length=ifelse(!is.na(total_length),total_length,.fitted))

eusthenopteron%>%
  lm(log(est_length)~log(OOL),.)%>%summary()
## 
## Call:
## lm(formula = log(est_length) ~ log(OOL), data = .)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.15257 -0.09672  0.02296  0.08062  0.13145 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  1.70976    0.07725   22.13 1.05e-11 ***
## log(OOL)     0.97824    0.02516   38.88 7.72e-15 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.09955 on 13 degrees of freedom
##   (36 observations deleted due to missingness)
## Multiple R-squared:  0.9915, Adjusted R-squared:  0.9908 
## F-statistic:  1512 on 1 and 13 DF,  p-value: 7.72e-15

OOL is also recovered as isometric with total_length for Eusthenopteron based on the data for Schultze (1984), though that study includes very few individuals between 40-80 cm.

6 Effect of phylogenetic position on regression model

6.1 Examining if residuals are consistent across major clades of fishes

OOL_residuals<-data_final%>%
  filter(!is.na(OOL))%>%
  filter(!is.na(total_length))%>%
  mutate(residuals=(residuals(lm(log(total_length)~log(OOL)))))

Mutating factor for more inclusive fish clades

residuals2<-OOL_residuals%>%
  filter(!is.na(residuals),length_as!="estimated t.l.",
         !(extinct==T&clade=="Chondrichthyes"))%>%
  group_by(taxon)%>%
  summarize(residuals=mean(residuals),order=unique(order),
            family=unique(family),shape=unique(shape),higher_group=unique(higher_group))%>%
  mutate(higher_group=factor(higher_group,levels=c("Petromyzontiformes","Arthrodira","Chondrichthyes","Sarcopterygii","Basal Actinopterygii","Basal Teleostei","Ostariophysi","Stem Euteleostei","Acanthopterygii")))%>%
  arrange(higher_group,order,taxon)
level_info <- residuals2 %>%
  arrange(higher_group, order) %>% 
  pull(order) %>% 
  unique()
residuals2<-residuals2%>%
  mutate(order=factor(order,levels=rev(level_info)))

Note, some of the clades used here (i.e., “Basal Actinopterygii”, “Stem Euteleostei”) are paraphyletic, but this was done in order to examine the major trends in the evolution of OOL proportions across fishes.

ggplot(residuals2,
  aes(residuals,y=order))+
  geom_vline(xintercept=0,linetype="dashed")+
  geom_violin(aes(fill=higher_group),scale="width")+
  labs(y="Higher Clade",x="Residuals",fill="Higher Clade",color="Higher Clade")+
  geom_boxplot()+
  theme(legend.position=c(0.8,0.3))
Box and whisker plot of the species-average residuals (excluding known juveniles) of the OOL ~ total length regression model grouped by order, sorted and color-coded by major fish clade.

Figure 6.1: Box and whisker plot of the species-average residuals (excluding known juveniles) of the OOL ~ total length regression model grouped by order, sorted and color-coded by major fish clade.

ggplot(residuals2,aes(y=residuals,x=higher_group))+
  geom_hline(yintercept=0,linetype="dashed")+
  geom_violin(aes(fill=higher_group),scale="width")+
  geom_boxplot(width=0.3)+
  labs(x="Higher Clade",y="Residuals")+
  theme(legend.position="none",axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))
Box and whisker plot of the species-average residuals (excluding known juveniles) of the OOL ~ total length regression model, grouped by major fish clade.

Figure 6.2: Box and whisker plot of the species-average residuals (excluding known juveniles) of the OOL ~ total length regression model, grouped by major fish clade.

6.2 Testing if Acanthoptergyii have statistically larger heads than other fishes

6.2.1 Testing residuals of OOL versus total length vary significantly between acanthopterygians and other fishes.

residuals2%>%
  mutate(higher_group=relevel(higher_group,"Chondrichthyes"))%$%
  lm(residuals~higher_group,.)%>%
  summary()
## 
## Call:
## lm(formula = residuals ~ higher_group, data = .)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.70861 -0.14209 -0.01925  0.10385  1.46668 
## 
## Coefficients:
##                                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                       0.019697   0.016728   1.178  0.23928    
## higher_groupPetromyzontiformes    0.009651   0.081306   0.119  0.90554    
## higher_groupArthrodira           -0.077369   0.073106  -1.058  0.29018    
## higher_groupSarcopterygii         0.002772   0.131004   0.021  0.98312    
## higher_groupBasal Actinopterygii  0.181811   0.058696   3.097  0.00201 ** 
## higher_groupBasal Teleostei       0.108875   0.042598   2.556  0.01074 *  
## higher_groupOstariophysi          0.020555   0.023689   0.868  0.38578    
## higher_groupStem Euteleostei      0.066738   0.036537   1.827  0.06807 .  
## higher_groupAcanthopterygii      -0.091342   0.019564  -4.669 3.46e-06 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.225 on 962 degrees of freedom
## Multiple R-squared:  0.0812, Adjusted R-squared:  0.07356 
## F-statistic: 10.63 on 8 and 962 DF,  p-value: 2.196e-14

Basal actinopterygians and acanthopterygians were found to have statistically detectable residuals from the base level of Chondrichthyes. With basal actinopterygians, this is likely due to the large number of gars (Lepisosteidae) and elongate-bodied sturgeons (i.e., Pseudoscaphirhynchus) considered relative to the diversity of the clade. However, for Acanthopterygii this is harder to explain because a wide diversity of acanthopterygians were sampled.

residuals2%>%
  mutate(shape=relevel(as.factor(shape),"fusiform"),
         higher_group=relevel(higher_group,"Chondrichthyes"))%$%
  lm(residuals~higher_group+shape,.)%>%
  summary()
## 
## Call:
## lm(formula = residuals ~ higher_group + shape, data = .)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.57877 -0.11309 -0.00095  0.10697  1.19325 
## 
## Coefficients:
##                                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                      -0.06016    0.01617  -3.719 0.000211 ***
## higher_groupPetromyzontiformes   -0.41144    0.08181  -5.029 5.88e-07 ***
## higher_groupArthrodira           -0.01853    0.06405  -0.289 0.772446    
## higher_groupSarcopterygii         0.08263    0.11454   0.721 0.470846    
## higher_groupBasal Actinopterygii  0.13033    0.05199   2.507 0.012349 *  
## higher_groupBasal Teleostei      -0.06444    0.04167  -1.546 0.122337    
## higher_groupOstariophysi          0.08719    0.02169   4.020 6.28e-05 ***
## higher_groupStem Euteleostei      0.01243    0.03258   0.382 0.702859    
## higher_groupAcanthopterygii      -0.03227    0.01815  -1.778 0.075703 .  
## shapeanguilliform                 0.50095    0.04094  12.236  < 2e-16 ***
## shapecompressiform               -0.05127    0.02071  -2.476 0.013454 *  
## shapeelongate                     0.21014    0.02007  10.473  < 2e-16 ***
## shapeflattened                    0.08950    0.05434   1.647 0.099911 .  
## shapemacruriform                  0.40665    0.04922   8.262 4.74e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1964 on 957 degrees of freedom
## Multiple R-squared:  0.3039, Adjusted R-squared:  0.2944 
## F-statistic: 32.13 on 13 and 957 DF,  p-value: < 2.2e-16

6.2.1.1 Same analysis, examining head length

Considering residuals of head length and total length by themselves

data_final%>%
  drop_na(head_length,total_length)%>%
  group_by(taxon)%>%
  summarise(higher_group=unique(higher_group),shape=unique(shape),
            head_length=mean(head_length),total_length=mean(total_length))%>%
  augment(lm(log(total_length)~log(head_length),.),newdata=.)%>%
  mutate(higher_group=relevel(as.factor(higher_group),"Chondrichthyes"))%$%
  lm(.resid~higher_group,.)%>%
  summary()
## 
## Call:
## lm(formula = .resid ~ higher_group, data = .)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.46077 -0.11452 -0.01988  0.09829  1.53835 
## 
## Coefficients:
##                                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                       0.063497   0.013666   4.646 3.85e-06 ***
## higher_groupAcanthopterygii      -0.139962   0.015984  -8.757  < 2e-16 ***
## higher_groupArthrodira            0.077506   0.059727   1.298   0.1947    
## higher_groupBasal Actinopterygii -0.028837   0.047954  -0.601   0.5478    
## higher_groupBasal Teleostei       0.190382   0.034802   5.470 5.72e-08 ***
## higher_groupOstariophysi         -0.008899   0.019354  -0.460   0.6458    
## higher_groupPetromyzontiformes   -0.072814   0.066426  -1.096   0.2733    
## higher_groupSarcopterygii         0.045563   0.107029   0.426   0.6704    
## higher_groupStem Euteleostei      0.055367   0.029850   1.855   0.0639 .  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1839 on 962 degrees of freedom
## Multiple R-squared:  0.1813, Adjusted R-squared:  0.1745 
## F-statistic: 26.63 on 8 and 962 DF,  p-value: < 2.2e-16

Adding in shape as an additional categorical variable

data_final%>%
  drop_na(head_length,total_length)%>%
  group_by(taxon)%>%
  summarise(higher_group=unique(higher_group),shape=unique(shape),
            head_length=mean(head_length),total_length=mean(total_length))%>%
  augment(lm(log(total_length)~log(head_length)+shape,.),newdata=.)%>%
  mutate(higher_group=relevel(as.factor(higher_group),"Chondrichthyes"))%$%
  lm(.resid~higher_group,.)%>%
  summary()
## 
## Call:
## lm(formula = .resid ~ higher_group, data = .)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.65484 -0.10061 -0.00623  0.09422  1.21323 
## 
## Coefficients:
##                                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                       0.057397   0.012272   4.677 3.33e-06 ***
## higher_groupAcanthopterygii      -0.111475   0.014353  -7.767 2.06e-14 ***
## higher_groupArthrodira            0.095077   0.053633   1.773   0.0766 .  
## higher_groupBasal Actinopterygii -0.052799   0.043061  -1.226   0.2204    
## higher_groupBasal Teleostei       0.022120   0.031251   0.708   0.4792    
## higher_groupOstariophysi          0.007456   0.017379   0.429   0.6680    
## higher_groupPetromyzontiformes   -0.461448   0.059649  -7.736 2.58e-14 ***
## higher_groupSarcopterygii         0.087313   0.096109   0.908   0.3639    
## higher_groupStem Euteleostei      0.007610   0.026805   0.284   0.7765    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1651 on 962 degrees of freedom
## Multiple R-squared:  0.1549, Adjusted R-squared:  0.1478 
## F-statistic: 22.04 on 8 and 962 DF,  p-value: < 2.2e-16

However, in the case of head length acanthopterygians still stand out as having larger than expected heads even after adding in shape as an additional explanatory variable. This could be due to phylogenetic signal, and is worth testing with phylogenetic comparative methods in a future analysis, but in the context of the present study makes it worth testing if acanthopterygian fishes have an undue influence on length regressions for Dunkleosteus.

Notably, Petromyzontiformes also have heads that are larger than expected for anguilliform fishes. This may be because Petromyzontiformes are not anguilliform because of specializations for axial elongation, but because they diverged from other vertebrates prior to the evolution of limb girdles. Thus, lampreys may not be truly anguilliform but “fusiform fishes without fins”.

Also, note that in these analyses arthrodires are recovered as, on average, not having significantly different head-body proportions from elasmobranchs and other non-acanthopterygian fishes.

6.3 Absolute residuals of the model by family

ggplot(residuals2%>%
         mutate(abs_residuals=abs(residuals)),
       aes(x=abs_residuals,y=reorder(family,desc(abs_residuals))))+
  geom_vline(xintercept=0,linetype="dashed")+
  geom_violin(aes(fill=higher_group),scale="width")+
  geom_boxplot(width=0.3)+
  labs(y="Family",x="Absolute Residuals")+
  theme(legend.position="none",axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  coord_cartesian(xlim=c(0,0.6))
Box-and-whisker plot of the absolute residuals of the model sorted by family, showing how the taxa with the lowest error rates tend to be those with generalized, fusiform body plans.

Figure 6.3: Box-and-whisker plot of the absolute residuals of the model sorted by family, showing how the taxa with the lowest error rates tend to be those with generalized, fusiform body plans.

7 Effect of body shape on regression model

7.1 Graph of OOL versus total length color-coded by shape

data_final%>%
  drop_na(shape)%>%
  ggplot(aes(y=total_length,x=OOL))+
  geom_star(aes(fill=shape,starshape=clade))+
  scale_starshape_manual(values=c(15,13,11,1,28),guide="none")+
  geom_smooth(formula=y~x,method="lm")+
  geom_smooth(formula=y~x,method="lm")+
  labs(fill="Body Shape",starshape="Clade",
       x="Orbit-Opercular Length (cm)",y="Total Length (cm)")+
  scale_x_continuous(trans='log10')+
  scale_y_continuous(trans='log10')+
  theme_classic()+
  theme(legend.position=c(0.85,0.25))+
  guides(fill = guide_legend(override.aes = list(starshape = 15)))
Graph of OOL versus total length (plotted on a log10 scale) color-coded by body shape

Figure 7.1: Graph of OOL versus total length (plotted on a log10 scale) color-coded by body shape

7.2 Testing to see if different body shapes have different regression lines

7.2.1 Testing for differences in intercept

options(width=1000)
#Testing for differences in intercept between body shape groups
summary(lm(log(total_length)~log(OOL)+shape,
           data_final))
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) + shape, data = data_final)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.64817 -0.09960 -0.00015  0.10395  1.38686 
## 
## Coefficients:
##                     Estimate Std. Error t value Pr(>|t|)    
## (Intercept)         1.864814   0.008083 230.721  < 2e-16 ***
## log(OOL)            0.979538   0.003532 277.362  < 2e-16 ***
## shapeanguilliform   0.246846   0.009810  25.163  < 2e-16 ***
## shapecompressiform -0.097574   0.012227  -7.980 2.03e-15 ***
## shapeelongate       0.254968   0.009766  26.109  < 2e-16 ***
## shapeflattened      0.079619   0.036772   2.165   0.0304 *  
## shapemacruriform    0.435956   0.030099  14.484  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1859 on 3162 degrees of freedom
##   (53 observations deleted due to missingness)
## Multiple R-squared:  0.964,  Adjusted R-squared:  0.9639 
## F-statistic: 1.412e+04 on 6 and 3162 DF,  p-value: < 2.2e-16

7.2.2 Testing for differences in slope between groups

#Testing for differences in slope between body shape groups
summary(lm(log(total_length)~log(OOL)*shape,
           data_final))
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) * shape, data = data_final)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.62517 -0.09867 -0.00170  0.10336  1.39119 
## 
## Coefficients:
##                              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                  1.877576   0.008716 215.406  < 2e-16 ***
## log(OOL)                     0.973000   0.003911 248.802  < 2e-16 ***
## shapeanguilliform            0.244345   0.041452   5.895 4.15e-09 ***
## shapecompressiform          -0.109844   0.028747  -3.821 0.000135 ***
## shapeelongate                0.134659   0.026878   5.010 5.74e-07 ***
## shapeflattened              -0.048682   0.236282  -0.206 0.836779    
## shapemacruriform             0.381928   0.096882   3.942 8.25e-05 ***
## log(OOL):shapeanguilliform   0.001095   0.021337   0.051 0.959057    
## log(OOL):shapecompressiform  0.006197   0.017716   0.350 0.726504    
## log(OOL):shapeelongate       0.054360   0.011317   4.803 1.63e-06 ***
## log(OOL):shapeflattened      0.051227   0.090326   0.567 0.570662    
## log(OOL):shapemacruriform    0.024145   0.039368   0.613 0.539715    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1854 on 3157 degrees of freedom
##   (53 observations deleted due to missingness)
## Multiple R-squared:  0.9643, Adjusted R-squared:  0.9642 
## F-statistic:  7747 on 11 and 3157 DF,  p-value: < 2.2e-16

7.2.3 ANOVA of residuals versus body shape

data_final%>%
  broom::augment(fit.OOL,newdata=.)%>%
  lm(.resid~shape,.)%>%
  anova()
data_final%>%
  broom::augment(fit.OOL,newdata=.)%>%
  lm(.resid~shape,.)%>% summary()
## 
## Call:
## lm(formula = .resid ~ shape, data = .)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.62907 -0.09982 -0.00154  0.10634  1.37362 
## 
## Coefficients:
##                     Estimate Std. Error t value Pr(>|t|)    
## (Intercept)        -0.068454   0.004233 -16.172  < 2e-16 ***
## shapeanguilliform   0.247953   0.009840  25.199  < 2e-16 ***
## shapecompressiform -0.089115   0.012136  -7.343 2.64e-13 ***
## shapeelongate       0.250026   0.009742  25.665  < 2e-16 ***
## shapeflattened      0.069075   0.036827   1.876   0.0608 .  
## shapemacruriform    0.429435   0.030168  14.235  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1865 on 3163 degrees of freedom
##   (53 observations deleted due to missingness)
## Multiple R-squared:  0.3123, Adjusted R-squared:  0.3112 
## F-statistic: 287.2 on 5 and 3163 DF,  p-value: < 2.2e-16

7.3 Box-and-whisker plot of residuals by body shape

ggplot(OOL_residuals%>%
         mutate(shape=factor(shape,levels=c("macruriform","flattened","anguilliform",
                                            "elongate","fusiform","compressiform"))) %>%
         filter(ontogeny %in% c("adult","subadult")|is.na(ontogeny)) %>%
         group_by(taxon) %>%
         summarise(residuals=mean(residuals),shape=unique(shape)),
       aes(x=shape,y=residuals))+
  geom_hline(yintercept=0,linetype="dashed")+
  geom_violin(aes(fill=shape),scale="width")+
  geom_boxplot(width=0.3)+
  labs(x="Shape",y="Residuals")+
  theme(legend.position="none")
Box and whisker plot of residuals in the dataset categorized by body shape, showing how fishes with elongate or anguilliform bodies are characterized by more negative residuals and fishes with shorter (compressiform) bodies are characterized by more positive residuals. Data points are average residuals for each species with known juveniles removed.

Figure 7.2: Box and whisker plot of residuals in the dataset categorized by body shape, showing how fishes with elongate or anguilliform bodies are characterized by more negative residuals and fishes with shorter (compressiform) bodies are characterized by more positive residuals. Data points are average residuals for each species with known juveniles removed.

OOL_residuals%>%
  filter(shape=="fusiform",!family %in% c("Acanthuridae","Monacanthidae","Balistidae"))%>%
  mutate(shape2=factor(case_when(family=="Holocentridae"~"Holocentridae",
                          family=="Serranidae"~"Serranidae",
                          TRUE ~ "Other Fusiform Fishes"),
                       ordered=T,levels=c("Holocentridae","Serranidae","Other Fusiform Fishes")))%>%
  group_by(shape2)%>%
  summarise(mean=mean(residuals),
            max=max(residuals),
            min=min(residuals),
            lwr=mean-2*sd(residuals),
            upr=mean+2*sd(residuals))%>%
  mutate(CI=paste0("(",round(lwr,3),"-",round(upr,3),")"),
         range=paste0("(",round(min,3),"-",round(max,3),")"))%>%
  select(shape2,mean,CI,range)%>%
  kable(digits=3,
        caption="Mean, 95% confidence interval, and range of residuals in fusiform fishes, showing how in general fusiform fishes (i.e., non-grouper fishes) have residuals that span 0.",
        col.names=c("Group","Mean","95% C.I.","Range"))%>%
  kable_styling()
Table 7.1: Mean, 95% confidence interval, and range of residuals in fusiform fishes, showing how in general fusiform fishes (i.e., non-grouper fishes) have residuals that span 0.
Group Mean 95% C.I. Range
Holocentridae -0.291 (-0.426–0.155) (-0.374–0.166)
Serranidae -0.351 (-0.619–0.082) (-0.589-0.149)
Other Fusiform Fishes -0.037 (-0.351-0.276) (-0.518-0.804)

7.4 Comparing regression lines for different body proportions

The primary purpose of this was to see if complete arthrodires can reliably be described as fusiform in body shape, given a known length and width.

7.4.1 Comparing regression lines for total length and body depth

data_final%>%
  drop_na(body_depth)%>%
  ggplot(aes(total_length,body_depth))+
  geom_star(aes(color=shape,starshape=clade))+
  geom_star(aes(fill=shape,starshape=clade))+
  scale_starshape_manual(values=c(15,13,28,1,11))+
  scale_x_continuous(trans="log10")+
  scale_y_continuous(trans="log10")+
  geom_smooth(aes(color=shape),method="lm",formula=y~x,se=F)+
  geom_smooth(method="lm",formula=y~x)+
  labs(y="Body Depth (cm)",x="Total Length (cm)",
       color="Body Shape",starshape="Clade",fill="Body Shape")+
  geom_star(data=.%>%filter(clade=="Placodermi"),
            aes(starshape=clade),color="white",starstroke=0.33,size=3,fill="black",show.legend=F)+
  guides(fill=guide_legend(override.aes = list(shape = 15)),
         color=guide_legend(override.aes = list(shape = 15)))
Plotting regression lines for different levels of body shape

Figure 7.3: Plotting regression lines for different levels of body shape

7.4.2 Comparing regression lines for total length and proportional body depth

data_final%>%
  drop_na(body_depth,total_length)%>%
  ggplot(aes(x=total_length,y=body_depth/total_length))+
  geom_star(aes(color=shape,starshape=clade))+
  geom_star(aes(fill=shape,starshape=clade))+
  geom_star(data=data_final%>%filter(clade=="Placodermi"),
            aes(starshape=clade),color="white",starstroke=0.33,size=3,fill="black",show.legend=F)+
  scale_x_continuous(trans="log10")+
  scale_starshape_manual(values=c(15,13,11,1,28))+
  labs(y="Body Depth (as % of total length)",x="Total Length (cm)",
       starshape="Clade",color="Body Shape",fill="Body Shape")+
  geom_smooth(aes(color=shape),method="lm",formula=y~x,se=F)+
  geom_smooth(method="lm",formula=y~x)+
  guides(fill=guide_legend(override.aes = list(shape = 15)),
         color=guide_legend(override.aes = list(shape = 15)))

7.4.3 Comparing total length against body width

data_final%>%
  filter(clade %in% c("Actinopterygii","Chondrichthyes","Placodermi","Sarcopterygii")) %>%
  drop_na(body_width)%>%
  ggplot(aes(x=total_length,y=body_width))+
  labs(y="Body Width (cm)",x="Total Length (cm)",
       color="Shape",fill="Shape",starshape="Clade")+
  geom_point(aes(color=shape),shape=21)+
  geom_star(aes(fill=shape,starshape=clade))+
  scale_x_continuous(trans='log10')+
  scale_y_continuous(trans='log10')+
  scale_starshape_manual(values=c(15,13,1,28))+
  geom_smooth(aes(color=shape),method="lm",formula=y~x,se=F)+
  geom_smooth(method="lm",formula=y~x)+
  geom_star(data=data_final%>%filter(clade=="Placodermi"),size=3,fill="white")+
  theme_classic()+
  guides(fill=guide_legend(override.aes = list(shape = 15)))

Based on this, it appears that arthrodires known from complete remains show a shape similar enough to fusiform fishes to categorize them as such (with the possible exception of Amazichthys). However, among fusiform fishes arthrodires show aspects of body shape that significantly differentiate them from both actinopterygians and elasmobranchs (see next section).

7.5 Body shape diversity in fishes

7.5.1 Body height versus body width

For this section, total length for arthrodires for which incomplete remains are known was estimated using the model including body shape as a covariate and allowing slope to vary between Chondrichthyans and all other fishes, which is considered the best-fitting equation here, in order to highlight differences in overall body shape between arthrodires and all other fishes.

ggplot(data_final %>% 
         drop_na(body_depth,body_width)%>%
         filter(clade %in%
                          c("Actinopterygii","Chondrichthyes","Placodermi","Sarcopterygii")),
       aes(body_depth,body_width))+
  geom_star(aes(fill=clade,starshape=clade),size=1.5)+
  geom_star(data=.%>%filter(clade=="Placodermi"),aes(fill=clade,starshape=clade),size=3,
            show.legend=F)+
  scale_starshape_manual(values=c(15,13,1,28))+
  scale_x_continuous(trans="log10")+
  scale_y_continuous(trans="log10")+
  geom_abline(slope=1,linetype="dashed")+
  labs(x="Body Height (cm)",y="Body Width (cm)",color="Clade",fill="Clade",starshape="Clade")+
  theme(legend.position=c(0.8,0.2))
Dotted line represents a line with slope of 1 (i.e., representing fishes with a body equal in height and width), showing how arthrodires and chondrichthyans generally have bodies that are subcircular in cross-section, whereas actinopterygians generally have bodies that are much deeper than wide

Figure 7.4: Dotted line represents a line with slope of 1 (i.e., representing fishes with a body equal in height and width), showing how arthrodires and chondrichthyans generally have bodies that are subcircular in cross-section, whereas actinopterygians generally have bodies that are much deeper than wide

As can be seen from this graph, chondrichthyans, arthrodires, and what few sarcopterygians could be considered in this analysis generally have a body that is subcircular in cross-section (width and height near equal), whereas most actinopterygians have a body that is much deeper relative to its width.

7.5.2 Boxplot of body_depth/body_width across fishes

data_final%>%
  drop_na(body_width,body_depth)%>%
  mutate(clade2=case_when(genus %in% c("Dunkleosteus","Paramylostoma",
                                       "Bungartius","Heintzichthys") ~ "Cleveland Shale Arthrodira",
                          clade=="Placodermi"~"Other Arthrodira",
                          genus %in% c("Thunnus","Katsuwonus","Allothunnus",
                                       "Auxis","Euthynnus","Sarda","Cybiosarda","Gymnosarda",
                                       "Orcynopsis") ~ "Thunnini and Sardini",
                          order=="Siluriformes" ~ "Siluriformes",
                          higher_group=="Basal Actinopterygii"~"Basal Actinopterygii",
                          clade=="Actinopterygii" ~ "Other Teleostei",
                          clade=="Chondrichthyes" ~ "Chondrichthyes",
                          clade == "Sarcopterygii" ~ "Sarcopterygii")) %>%
  mutate(body_ratio=body_depth/body_width,
         clade2=factor(clade2,ordered=T,
                       levels=c("Cleveland Shale Arthrodira",
                                "Other Arthrodira",
                                "Chondrichthyes",
                                "Basal Actinopterygii",
                                "Siluriformes",
                                "Thunnini and Sardini",
                                "Other Teleostei",
                                "Sarcopterygii"))) %>%
  ggplot(aes(clade2,body_ratio))+
  geom_hline(yintercept=1,linetype="dashed")+
  geom_violin(aes(fill=clade2),scale="width",show.legend=F)+
  geom_boxplot(width=0.3)+
  scale_fill_manual(values=c(hue_pal()(7)))+
  scale_x_discrete(labels = wrap_format(16))+
  labs(y="Body Depth/Body Width",x="Clade")+
  coord_cartesian(ylim=c(0,4))+
  theme(axis.text.x = element_text(angle = 45,vjust = 1, hjust=1))
Box plot of body depth/body width ratio in fishes examined in this study

Figure 7.5: Box plot of body depth/body width ratio in fishes examined in this study

The outlier point among “Other Arthrodira” is Eastmanosteus calliaspis, a pachyosteomorph arthrodire that shows a mediolaterally narrower body than other taxa. The Cleveland Shale arthrodire that represents an outlier is CMNH 7424, a juvenile specimen of D. terrelli with a much more circular body cross-section, more similar to coccosteomorphs. Note that all of the “Other Arthrodires” considered here are non-pachyosteomorph taxa. Amazichthys trinajsticae could not be included as its body_width is unclear.

7.5.3 Body height versus total length in fusiform fishes

fit.shape_clade3<-lm(log(total_length)~log(OOL)*(clade=="Chondrichthyes")+
                       shape+(family=="Serranidae")+(family=="Holocentridae"),
                    data_final)

data_final %>%
  filter(!is.na(body_depth),shape=="fusiform"|clade=="Placodermi")%>%
  mutate(total_length=ifelse(is.na(total_length),
                             exp(predict(fit.shape_clade3,.))*
                               regression.stats(fit.shape_clade3)$CF,
                             total_length))%>%
         arrange(clade)%>%
  ggplot(aes(total_length,body_depth))+
  geom_star(aes(fill=clade,starshape=clade),size=1.5)+
  scale_starshape_manual(values=c(15,13,1,28))+
  geom_smooth(method="lm",formula=y~x,aes(color=clade),se=F)+
  geom_star(data=.%>%filter(clade=="Placodermi"),aes(fill=clade,starshape=clade),size=3,
            show.legend=F)+
  scale_x_continuous(trans="log10")+
  scale_y_continuous(trans="log10")+
  labs(x="Total Length (cm)",y="Body Depth (cm)",color="Clade",
       fill="Clade",starshape="Clade")+
  theme(legend.position=c(0.8,0.2))
Plot of body depth versus total length in fusiform fishes. Note how arthrodires and sarcopterygians have deeper bodies than chondrichthyans despite lacking the very mediolaterally narrow bodies seen in actinopterygians, and indeed actinopterygians often have deeper bodies than similar-sized arthrodires except in the very deep-bodied Dunkleosteus

Figure 7.6: Plot of body depth versus total length in fusiform fishes. Note how arthrodires and sarcopterygians have deeper bodies than chondrichthyans despite lacking the very mediolaterally narrow bodies seen in actinopterygians, and indeed actinopterygians often have deeper bodies than similar-sized arthrodires except in the very deep-bodied Dunkleosteus

However, arthrodires have much deeper bodies than sharks at similar body lengths, more comparable to actinopterygian and sarcopterygian fishes. This includes the otherwise very shark-like Amazichthys trinajsticae. There are only two taxa which do not. The first is Gymnotrachelus hydei, which has a very unusual armor shape (narrow body, anteroposteriorly short armor relative to the head) that suggests it may eventually be found out to pertain to a highly elongate-bodied or anguilliform taxon. The other is the reconstruction of Titanichthys clarki in Boyle and Ryan (2017). However, the value for this taxon may be an underestimate because…

  1. The reconstructed specimen is a subadult, and body depth increases through ontogeny in Dunkleosteus terrelli
  2. The reconstruction is missing its ventral shield (body height here is an approximation), and thus the addition of the ventral armor may make the body deeper.
  3. The reconstructed height of the torso may be an underestimate, as the mediodorsal seems to be too low to allow the head-raising musculature to easily attach to it and also maintain a streamlined body shape.

7.5.4 Body width versus total length in fusiform/elongate fishes

ggplot(data_final %>% filter(!is.na(body_width),
                             shape %in% c("elongate","fusiform"))%>%
         mutate(total_length=ifelse(is.na(total_length),
                                    exp(predict(fit.shape_clade3,.))*
                                      regression.stats(fit.shape_clade3)$CF,
                                    total_length),
                clade=ifelse(order %in% c("Siluriformes"),
                             "Actinopterygii (Siluriformes)",clade),
                clade=ifelse(family %in% c("Scombridae"),
                             "Actinopterygii (Scombridae)",clade))%>%
         arrange(clade),
       aes(total_length,body_width))+
  geom_smooth(method="lm",formula=y~x,aes(color=clade),se=F)+
  geom_star(aes(color=clade,starshape=clade),
            .%>%filter(clade=="Sarcopterygii"),size=1.5)+
  geom_star(aes(fill=clade,starshape=clade),size=1.5)+
  scale_starshape_manual(values=c(15,15,15,13,1,28))+
  scale_color_manual(values=c(hue_pal()(3)[1],"#c4342a","#fbb2ad",
                             hue_pal()(3)[2],"black",hue_pal()(3)[3]))+
  scale_fill_manual(values=c(hue_pal()(3)[1],"#c4342a","#fbb2ad",
                             hue_pal()(3)[2],"black",hue_pal()(3)[3]))+
  geom_smooth(method="lm",formula=y~x,aes(color=clade),se=F,show.legend=F)+
  geom_star(data=.%>%filter(clade=="Placodermi"),aes(fill=clade,starshape=clade),
            color="white",starstroke=0.33,size=3,show.legend=F)+
  scale_x_continuous(trans="log10")+
  scale_y_continuous(trans="log10")+
  labs(x="Total Length (cm)",y="Body Width (cm)",color="Clade",
       fill="Clade",starshape="Clade",alpha="Clade")+
  theme(legend.position=c(0.8,0.2))
Plot of body width versus total length in fusiform and elongate-bodied fishes. Total length is estimated for arthrodires for which complete remains are unknown.

Figure 7.7: Plot of body width versus total length in fusiform and elongate-bodied fishes. Total length is estimated for arthrodires for which complete remains are unknown.

Based on this, it is clear that arthrodires have much wider bodies than most chondrichthyans or typical actinopterygians. The only fishes that have similar body proportions to arthrodires in this regard are catfishes (Siluriformes), tuna-like scombrids (i.e., taxa like Thunnus or Sarda), and sarcopterygians (not shown in figure due to low number of data points).

7.6 Correlation between aspect ratio and percent head length

grid.arrange(
ggplot(data_final %>%
         drop_na(precaudal_length,head_length,body_depth,shape) %>%
         filter(length_as=="total length")%>%
         group_by(taxon)%>% summarise(across(c(head_length,precaudal_length,body_depth),
                                             mean),
                                      shape=unique(shape),clade=unique(clade),genus=unique(genus),family=unique(family)),
       aes(y=body_depth/precaudal_length,x=head_length/precaudal_length))+
  geom_star(aes(fill=shape,starshape=clade))+
  geom_star(data=.%>%filter(family %in% c("Balistidae","Monacanthidae","Acanthuridae")),
              aes(starshape=clade),fill="white")+
  geom_star(data=.%>%filter(family=="Serranidae"),
            aes(starshape=clade),fill="black")+
  scale_starshape_manual(values=c(15,13,12,1,12))+
  labs(x="Percent Head Length",y="Aspect Ratio of Body")+
  geom_vline(aes(xintercept=mean(head_length/precaudal_length)),linetype="dashed")+
  theme_classic()+
  theme(legend.position = "none"),
ggplot(data_final %>%
         drop_na(precaudal_length,OOL,body_depth,shape) %>%
         filter(length_as=="total length")%>%
         group_by(taxon)%>% summarise(across(c(OOL,precaudal_length,body_depth),
                                             mean),
                                      shape=unique(shape),clade=unique(clade),genus=unique(genus),family=unique(family)),
       aes(y=body_depth/precaudal_length,x=OOL/precaudal_length))+
  geom_star(aes(fill=shape,starshape=clade))+
  geom_star(data=.%>%filter(family %in% c("Balistidae","Monacanthidae","Acanthuridae")),
            aes(starshape=clade),fill="white")+
  geom_star(data=.%>%filter(family=="Serranidae"),
            aes(starshape=clade),fill="black")+
  scale_starshape_manual(values=c(15,13,28,1,11))+
  labs(x="Percent Orbit-Opercular Length",y="Aspect Ratio of Body",
       starshape="Clade",fill="Shape")+
  geom_vline(aes(xintercept=mean(OOL/precaudal_length)),linetype="dashed")+
  theme_classic()+
  theme(legend.position = "bottom")+
  guides(fill=guide_legend(ncol=2),
         starshape=guide_legend(ncol=2)),
heights=c(2,2.2)
)
Plot of (A) percent head length versus body aspect ratio (body depth/precaudal length) and (B) plot of percent OOL versus body aspect ratio, showing how for the most part head proportions are consistent in fishes of different body shapes but there is a slight effect of axial elongation. Black circles are groupers (Serranidae) and white circles are taxa with posteriorly shifted orbits (e.g., Balistidae).

Figure 7.8: Plot of (A) percent head length versus body aspect ratio (body depth/precaudal length) and (B) plot of percent OOL versus body aspect ratio, showing how for the most part head proportions are consistent in fishes of different body shapes but there is a slight effect of axial elongation. Black circles are groupers (Serranidae) and white circles are taxa with posteriorly shifted orbits (e.g., Balistidae).

As can be seen by this graph, there is a slight correlation between percent head length and axial elongation, particularly in more-elongate bodied taxa, but the correlation is not strong and it is not one that can be easily approximated by a linear model (i.e., its effect is much more pronounced on elongate taxa than it is in compressiform ones). In general, head-body proportions remain fairly consistent across fishes of diverse body shapes

7.7 Esting accuracy in species with a posteriorly shifted orbit using Balistes virescens

rbind(data_final%>%
        filter(references=="Afrisal et al. 2019"),
      data_final%>%
        filter(references=="Afrisal et al. 2019")%>%
        mutate(OOL=7.1))%>%
  data.frame()%>%
  mutate(fit=exp(predict(fit.OOL,.))*regression.stats(fit.OOL)$CF,
         PE=(fit-total_length)/fit,
         anterior_landmark=c("Anterior margin of orbit",
                             "Anterior margin of gill chamber"),
         taxon=str_replace(taxon,"_"," "))%>%
  select(taxon,anterior_landmark,OOL,total_length,fit,PE)%>%
  kable(digits=c(1,1,1,1,1,3),
        align=c("l","c","c","c","c","c"),
        col.names=c("Taxon","Anterior Landmark for OOL","OOL","Total Length","Estimated Length","PE"),
        caption="Comparison of error rates for OOL in species with a posteriorly shifted orbit using a representative skeletonized specimen of <i>Balistes virescens</i> from Afrisal et al. (2019)")%>%
  column_spec(1, italic = T)%>%
  kable_styling()
Table 7.2: Comparison of error rates for OOL in species with a posteriorly shifted orbit using a representative skeletonized specimen of Balistes virescens from Afrisal et al. (2019)
Taxon Anterior Landmark for OOL OOL Total Length Estimated Length PE
Balistoides virescens Anterior margin of orbit 4.6 46 31.3 -0.469
Balistoides virescens Anterior margin of gill chamber 7.1 46 48.0 0.043

Based on this, it looks like the reason for the high errors in taxa such as Balistoidei and many Acanthuriformes has nothing to do with the ventrally shifted gill opening. Instead, it appears to be due to a posterior shift in the orbit relative to the overall length of the skull in these taxa.

Thus, this means that in arthrodires (in which the gills are more ventral to the head than posterior like in other fishes) this shift in the branchial skeleton should not result in systematic errors in OOL. Indeed, it somewhat supports the shorter lengths for Dunkleosteus terrelli reported here, as such a shift typically only occurs in fishes with shorter bodies.

8 Comparing total lengths in placoderms with known total length

8.1 Predicting total length in complete Arthrodira

complete_arthrodire_length_estimates<-fossil_taxa%>%
  filter(!!fossil.specimens,genus!="Dunkleosteus")%>%
  augment(fit.OOL,newdata=.,interval="prediction")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.OOL)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit1",.)))%>%
  augment(fit.species_average,newdata=.,interval="prediction")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.species_average)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit2",.)))%>%
  mutate(across(.cols=c(total_length,
                        fit1.fitted,fit1.lower,fit1.upper,
                        fit2.fitted,fit2.lower,fit2.upper),round,2),
         fit1.range=paste0("(",sprintf("%.1f",round(fit1.lower,1)),"–",
                           sprintf("%.1f",round(fit1.upper,1)),")"),
         fit2.range=paste0("(",fit2.lower,"–",
                           sprintf("%.1f",round(fit2.upper,1)),")"),
         fit1.PE=paste0("(",sprintf("%.1f",round(fit1.fitted*(1-
                            regression.stats(fit.OOL)$adjPE/100),1)),
                    "–",sprintf("%.1f",round(fit1.fitted*(1+
                            regression.stats(fit.OOL)$adjPE/100),1)),")"),
         fit2.PE=paste0("(",sprintf("%.1f",round(fit2.fitted*(1-
                            regression.stats(fit.species_average)$adjPE/100),1)),
                    "–",sprintf("%.1f",round(fit2.fitted*(1+
                            regression.stats(fit.species_average)$adjPE/100),1)),")"),
         PE=((fit1.fitted-total_length)/fit1.fitted)%>%round(4),
         PE2=((fit2.fitted-total_length)/fit2.fitted)%>%round(4))
complete_arthrodire_length_estimates%>%
  rownames_to_column()%>%
  select(taxon,specimen,total_length,
         fit1.fitted,fit1.PE,fit1.range,PE,
         fit2.fitted,fit2.PE,fit2.range,PE2)%>%
  kable(digits=c(1,1,1,1,1,1,3,1,1,1,3),
        col.names=c("Taxon","Specimen","Actual","Est.","+/- PE","95% P.I.","%PE",
                    "Est.","+/- PE","95% C.I.","%PE"),
        align=c("l","c","c","c","c","c","c","c","c","c","c"),
        caption="Estimated lengths in specimens of arthrodires known from whole-body remains and in which total length is either measurable or can be approximated.")%>%
  column_spec(1, italic = T)%>%
  column_spec(c(4,8), bold = T)%>%
  add_header_above(c(" "=3,"Individual Data"=4,"Species Averages"=4))%>%
  kable_styling()
Table 8.1: Estimated lengths in specimens of arthrodires known from whole-body remains and in which total length is either measurable or can be approximated.
Individual Data
Species Averages
Taxon Specimen Actual Est. +/- PE 95% P.I. %PE Est. +/- PE 95% C.I. %PE
Millerosteus minor FMNH PF 1089 13.7 13.9 (11.4–16.3) (8.9–21.6) 0.011 14.0 (11.5–16.5) (8.83–22.1) 0.017
Millerosteus minor Composite Millerosteus 14.9 16.0 (13.2–18.9) (10.3–24.9) 0.068 16.1 (13.2–19.0) (10.18–25.5) 0.072
Africanaspis dorissa Gess and Trinajstic 2017 23.0 24.4 (20.2–28.7) (15.7–38.0) 0.059 24.4 (20.1–28.8) (15.44–38.6) 0.058
Incisoscutum ritchei Recon. (Trinajstic 2013) 30.3 31.6 (26.1–37.2) (20.4–49.1) 0.043 31.5 (25.8–37.1) (19.89–49.7) 0.038
Coccosteus cuspidatus NMS 1893.107.27 29.6 35.1 (28.9–41.3) (22.6–54.5) 0.156 34.9 (28.6–41.1) (22.05–55.1) 0.150
Coccosteus cuspidatus NMS 1897.55.6 32.3 36.0 (29.7–42.4) (23.2–56.0) 0.105 35.8 (29.4–42.2) (22.63–56.6) 0.098
Coccosteus cuspidatus FMNH PF 1673 37.1 36.5 (30.1–42.9) (23.5–56.7) -0.017 36.2 (29.8–42.7) (22.92–57.3) -0.024
Coccosteus cuspidatus Recon. (M & W 1968) 39.4 43.9 (36.2–51.7) (28.3–68.3) 0.103 43.5 (35.7–51.3) (27.51–68.8) 0.094
Coccosteus cuspidatus NMS 1900.12.12 34.4 43.1 (35.6–50.7) (27.8–67.0) 0.203 42.7 (35.1–50.3) (27.01–67.5) 0.195
Coccosteus cuspidatus ROM VP 52664 37.5 42.5 (35.1–50.0) (27.4–66.1) 0.118 42.1 (34.6–49.6) (26.64–66.6) 0.109
Plourdosteus canadensis MNHM 2-177 37.5 51.4 (42.4–60.4) (33.1–79.9) 0.270 50.8 (41.7–59.8) (32.11–80.3) 0.261
Dickosteus threiplandi NMS 1987.7.118 43.7 56.1 (46.3–66.0) (36.1–87.2) 0.222 55.4 (45.5–65.3) (35.02–87.6) 0.211
Holonema westolli Recon. (Miles 1971) 60.6 51.2 (42.2–60.2) (32.9–79.5) -0.185 50.6 (41.5–59.6) (31.98–80.0) -0.199
Watsonosteus fletti NMS G.1995.4.2 56.6 65.3 (53.8–76.8) (42.0–101.5) 0.133 64.3 (52.8–75.8) (40.65–101.7) 0.119
Gen. et sp. nov. CMNH 50233 63.0 68.5 (56.5–80.5) (44.1–106.4) 0.080 67.4 (55.4–79.4) (42.61–106.6) 0.065
Dickosteus threiplandi NHMUK PV P 49663 52.3 69.5 (57.3–81.7) (44.7–108.0) 0.247 68.3 (56.1–80.5) (43.21–108.1) 0.234
Amazichthys trinajsticae AA.MEM.DS.8 89.7 78.0 (64.3–91.7) (50.2–121.2) -0.150 76.6 (62.9–90.3) (48.45–121.2) -0.171
complete_arthrodire_length_estimates%>%
  filter(specimen %in% c("Composite Millerosteus","Gess and Trinajstic 2017",
                         "Recon. (Trinajstic 2013)","NMS 1893.107.27",
                         "FMNH PF 1089","ROM VP 52664","FMNH PF 1673",
                         "Recon. (M & W 1968)","NMS 1987.7.118","Recon. (Miles 1971)",
                         "MNHM 2-177","NMS G.1995.4.2","AA.MEM.DS.8"))%>%
  select(taxon,specimen,total_length,fit1.fitted,fit1.PE,fit1.range,PE,
         fit2.fitted,fit2.PE,fit2.range,PE2)%>%
  rename(Taxon=taxon,
         Specimen=specimen,
         "Actual Length"=total_length,
         "Estimated Length"=fit1.fitted,
         "+/- PE"=fit1.PE,
         "95% P.I."=fit1.range,
         Error=PE,
         "Estimated Length (Species Averages)"=fit2.fitted,
         "+/- PE  (Species Averages)"=fit2.PE,
         "95% P.I.  (Species Averages)"=fit2.range,
         "Error (Species Averages)"=PE2)%>%
  write.xlsx(file="Devonian Fish Tale Table 2 (Complete Arthrodire Estimates).xlsx")

Note: I am a little skeptical for the results of NHMUK PV P 49663. This specimen is very mangled and the skull and thoracic armor are a mangled mess of plates. It is very hard to see where the head ends and the body begins, as well as the margins of the orbit. Hence, the estimated length may be in error because it was hard to figure out where OOL was.

rbind("Mean Percent Error for Estimates Using Individual Data Points" = complete_arthrodire_length_estimates%>%pull(PE)%>%abs()%>%mean(),
"Mean Percent Error for Species Average Data" = complete_arthrodire_length_estimates%>%pull(PE2)%>%abs()%>%mean())%>%
  kable(digits=4,col.names="PE",align="c",
        caption="Mean error for arthrodires in which total length is known estimated via OOL",
        table.attr = "style='width:70%;'")%>%
  kable_styling()
Table 8.2: Mean error for arthrodires in which total length is known estimated via OOL
PE
Mean Percent Error for Estimates Using Individual Data Points 0.1275
Mean Percent Error for Species Average Data 0.1245

From this it can be seen that, without any other additional qualifiers, OOL estimates body length in arthrodires with an accuracy of about +/- 13.5%

8.2 Allometry of OOL in Arthrodira

8.2.1 Allometry of OOL in Coccosteomorpha

fossil_taxa%>%
  filter(clade=="Placodermi",
         length_as=="total length",
         !genus %in% c("Holonema","Amazichthys","Africanaspis","Newspecies"))%$%
  lm(log(OOL)~log(total_length),.)%>%
  summary()
## 
## Call:
## lm(formula = log(OOL) ~ log(total_length), data = .)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.17454 -0.05620 -0.01565  0.07205  0.15702 
## 
## Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       -2.24512    0.22556  -9.954 7.74e-07 ***
## log(total_length)  1.13551    0.06411  17.712 1.96e-09 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.09245 on 11 degrees of freedom
## Multiple R-squared:  0.9661, Adjusted R-squared:  0.963 
## F-statistic: 313.7 on 1 and 11 DF,  p-value: 1.959e-09

8.2.2 Allometry of OOL in all complete Arthrodira

fossil_taxa%>%
  filter(clade=="Placodermi",
         length_as=="total length")%$%
  lm(log(OOL)~log(total_length),.)%>%
  summary()
## 
## Call:
## lm(formula = log(OOL) ~ log(total_length), data = .)
## 
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -0.252590 -0.061796  0.007598  0.064107  0.217741 
## 
## Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       -1.70844    0.25591  -6.676 7.41e-06 ***
## log(total_length)  0.97069    0.07034  13.799 6.27e-10 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1369 on 15 degrees of freedom
## Multiple R-squared:  0.927,  Adjusted R-squared:  0.9221 
## F-statistic: 190.4 on 1 and 15 DF,  p-value: 6.267e-10

9 Predicting body length in Dunkleosteus terrelli

fossil_taxa%>%
  filter(!!fossil.specimens,genus=="Dunkleosteus")%>%
  augment(fit.OOL,newdata=.,interval="prediction")%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit1",.)))%>%
  mutate(across(fit1.fitted:fit1.upper,~exp(.)*regression.stats(fit.OOL)$CF),
         fit1.PE.lower=fit1.fitted*
           ((100-regression.stats(fit.OOL)$adjPE)/100),
         fit1.PE.upper=fit1.fitted*
           ((100+regression.stats(fit.OOL)$adjPE)/100),
         fit1.PE=paste0("(",round(fit1.PE.lower,1),"–",round(fit1.PE.upper,1),")"),
         fit1.range=paste0("(",round(fit1.lower,1),"–",round(fit1.upper,1),")"))%>%
  augment(fit.species_average,newdata=.,interval="prediction")%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit2",.)))%>%
  mutate(across(fit2.fitted:fit2.upper,~exp(.)*regression.stats(fit.species_average)$CF),
         fit2.PE.lower=fit2.fitted*((100-regression.stats(fit.species_average)$adjPE)/100),
         fit2.PE.upper=fit2.fitted*((100+regression.stats(fit.species_average)$adjPE)/100),
         fit2.PE=paste0("(",round(fit2.PE.lower,1),"–",round(fit2.PE.upper,1),")"),
         fit2.range=paste0("(",round(fit2.lower,1),"–",round(fit2.upper,1),")"))%>%
  select(specimen,fit1.fitted,fit1.PE,fit1.range,fit2.fitted,fit2.PE,fit2.range)%>%
  kable(digits=1,
        align=c("l","c","c","c","c","c","c"),
        caption= "Length estimates for specimens of <i>Dunkleosteus terrelli</i> using an all-species model, without any additional modifications or excluding specimens",
        col.names = c("Specimen","Est.","+/- %PE","95% P.I.",
                      "Est.","+/- %PE","95% P.I."))%>%
  add_header_above(c(" "=1,"Individual Specimens"=3,"Species Averages"=3))%>%
  column_spec(c(2,5), bold = T)%>%
  kable_styling()
Table 9.1: Length estimates for specimens of Dunkleosteus terrelli using an all-species model, without any additional modifications or excluding specimens
Individual Specimens
Species Averages
Specimen Est. +/- %PE 95% P.I. Est. +/- %PE 95% P.I.
CMNH 7424 195.2 (160.9–229.5) (125.6–303.4) 189.2 (155.4–223) (119.6–299.4)
CMNH 6090 293.0 (241.6–344.5) (188.5–455.5) 282.4 (232–332.8) (178.4–447.1)
CMNH 7054 305.7 (252.1–359.4) (196.7–475.2) 294.5 (241.9–347.1) (186–466.2)
CMNH 5768 352.6 (290.7–414.5) (226.8–548.1) 338.9 (278.4–399.4) (214–536.7)

9.1 Predicting total length excluding taxa with extreme body shapes

Note: “extreme body shapes” as defined here, includes anguiliform, compressiform, and macruriform taxa, as well as taxa with posteriorly shifted orbits like Balistoidei.

It seems highly unlikely that Dunkleosteus had an anguilliform, macruriform, or truly compressiform body plan (though a semi-compressiform lamnid-like body plan is possible). Similarly, Dunkleosteus clearly does not demonstrate the cranial features that cause some other taxa to exhibit high error rates, such as the posteriorly shifted orbits of Balistoidei or the elongate rostrum of Rhinochimaera or Mitsukurina. Ruling out the enlarged heads of groupers is more difficult (as one might expect Dunkleosteus to have a large head), but it seems like an unlikely inference given that other arthrodires show head-body proportions within the range of variation of “typical” (i.e., non-grouper) fishes. Thus, all of these taxa can be safely excluded from the regression model. However, it is not possible to fully rule out an elongate body plan as seen in Coryphaena or some Istiophoriformes, though there is no evidence from the preserved remains of Dunkleosteus to suggest that this taxon had this type of body plan.

Thus, a model was fit comprised solely of fusiform and elongate-bodied taxa, excluding those other taxa which exhibited discernable specializations that could be ruled out or were highly unlikely in Dunkleosteus.

fit.no_extreme_shapes<-lm(log(total_length)~log(OOL),
                  data_final%>%filter(!shape %in%
                                          c("anguiliform","compressiform","macruriform"),
                                        !family %in% c("Balistidae", "Monacanthidae", "Serranidae","Holocentridae")))

summary(fit.no_extreme_shapes)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL), data = data_final %>% 
##     filter(!shape %in% c("anguiliform", "compressiform", "macruriform"), 
##         !family %in% c("Balistidae", "Monacanthidae", "Serranidae", 
##             "Holocentridae")))
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.53960 -0.12634 -0.00626  0.11267  1.52559 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 1.962169   0.008480   231.4   <2e-16 ***
## log(OOL)    0.983551   0.003791   259.4   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1943 on 2658 degrees of freedom
##   (51 observations deleted due to missingness)
## Multiple R-squared:  0.962,  Adjusted R-squared:  0.962 
## F-statistic: 6.73e+04 on 1 and 2658 DF,  p-value: < 2.2e-16
regression.stats(fit.no_extreme_shapes)
fossil_taxa%>%
  filter(!!fossil.specimens,genus=="Dunkleosteus")%>%
  augment(fit.no_extreme_shapes,newdata=.,interval="prediction")%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit1",.)))%>%
  mutate(across(fit1.fitted:fit1.upper,~exp(.)*regression.stats(fit.no_extreme_shapes)$CF),
         fit1.PE.lower=fit1.fitted*((100-regression.stats(fit.no_extreme_shapes)$adjPE)/100),
         fit1.PE.upper=fit1.fitted*((100+regression.stats(fit.no_extreme_shapes)$adjPE)/100),
         fit1.PE=paste0("(",round(fit1.PE.lower,1),"–",round(fit1.PE.upper,1),")"),
         fit1.range=paste0("(",round(fit1.lower,1),"–",round(fit1.upper,1),")"))%>%
  augment(fit.species_average2,newdata=.,interval="prediction")%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit2",.)))%>%
  mutate(across(fit2.fitted:fit2.upper,~exp(.)*regression.stats(fit.no_extreme_shapes)$CF),
         fit2.PE.lower=fit2.fitted*((100-regression.stats(fit.no_extreme_shapes)$adjPE)/100),
         fit2.PE.upper=fit2.fitted*((100+regression.stats(fit.no_extreme_shapes)$adjPE)/100),
         fit2.PE=paste0("(",round(fit2.PE.lower,1),"–",round(fit2.PE.upper,1),")"),
         fit2.range=paste0("(",round(fit2.lower,1),"–",round(fit2.upper,1),")"))%>%
  select(specimen,fit1.fitted,fit1.PE,fit1.range,fit2.fitted,fit2.PE,fit2.range)%>%
  kable(digits=1,
        align=c("l","c","c","c","c","c","c"),
        caption= "Length estimates for specimens of <i>Dunkleosteus terrelli</i> using a model excluding anguilliform, short-bodied, and macruriform taxa",
        col.names = c("Specimen","Est.","+/- %PE","95% P.I.",
                      "Est.","+/- %PE","95% P.I."))%>%
  add_header_above(c(" "=1,"Individual Specimens"=3,"Species Averages"=3))%>%
  column_spec(c(2,5), bold = T)%>%
  kable_styling()
Table 9.2: Length estimates for specimens of Dunkleosteus terrelli using a model excluding anguilliform, short-bodied, and macruriform taxa
Individual Specimens
Species Averages
Specimen Est. +/- %PE 95% P.I. Est. +/- %PE 95% P.I.
CMNH 7424 197.4 (167.2–227.5) (134.8–288.9) 191.1 (161.9–220.3) (128.1–285.1)
CMNH 6090 294.7 (249.8–339.7) (201.3–431.5) 284.7 (241.2–328.1) (190.7–424.9)
CMNH 7054 307.4 (260.5–354.3) (209.9–450) 296.8 (251.5–342.1) (198.8–443)
CMNH 5768 353.8 (299.8–407.8) (241.7–518) 341.3 (289.2–393.4) (228.6–509.6)

9.2 Plot of estimated lengths for Dunkleosteus using orbit-opercular length

(dunkleosteus_length<-ggplot(data_final%>%
         cbind(exp(predict(lm(log(total_length)~log(OOL),
                              data_final%>%
                                filter(!shape %in% c("compressiform","anguiliform","macruriform"))%>%
                                filter(clade!="Placodermi")),
                           data_final,interval="prediction")))%>%
         filter(!clade %in% c("Petromyzontiformes")),
       aes(y=total_length,x=OOL))+
  geom_point(aes(shape=clade),color="dark grey",fill="light grey")+
  geom_smooth(method="lm",alpha=0.25,se=F)+
  geom_line(aes(y=lwr), color = "#3366FF", linetype = "dashed",alpha=0.5)+
  geom_line(aes(y=upr), color = "#3366FF", linetype = "dashed",alpha=0.5)+
  geom_errorbar(data=.%>%filter(specimen %in% c("CMNH 5768", "CMNH 7054", "CMNH 6090", "CMNH 7424")),
                aes(ymin=lwr,ymax=upr),width=0.1)+
  geom_point(data=.%>%filter(specimen %in% c("CMNH 5768", "CMNH 7054", "CMNH 6090", "CMNH 7424")),
             aes(shape=clade,y=fit),fill="black",size=2)+
  scale_shape_manual(values=c(21,24,22,23),guide="none")+
  scale_x_continuous(trans='log10')+
  scale_y_continuous(trans='log10')+
  labs(x="Orbit-Opercular Length (cm)",y="Total Length (cm)")+
  theme_classic()+
  theme(legend.position=c(0.8,0.25)))
## `geom_smooth()` using formula = 'y ~ x'

9.3 Data plotted on a detransformed scale

ggplot(data_final%>%
         drop_na(OOL)%>%
         filter(clade %in% c("Actinopterygii","Sarcopterygii","Chondrichthyes","Placodermi","Petromyzontiformes"))%>%
         augment(fit.OOL,newdata=.,interval="prediction")%>%
         mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.OOL)$CF)),
       aes(y=total_length,x=OOL))+
  coord_cartesian(y=c(0,600),x=c(0,90))+
  geom_polygon(data=data.frame(OOL=c(0,71.75,85),total_length=c(0,750,750)),
               alpha=0.5,fill="yellow")+
  geom_hline(yintercept = 460,linetype="dotted",color="red")+
  geom_segment(data=.%>%filter(!!fossil.specimens,genus=="Dunkleosteus"),
               aes(y=.lower,yend=.lower,x=-20,xend=OOL),
               linetype="dashed",alpha=0.75)+
  geom_segment(data=.%>%filter(!!fossil.specimens,genus=="Dunkleosteus"),
               aes(y=.upper,yend=.upper,x=-20,xend=OOL),
               linetype="dashed",alpha=0.75)+
  scale_shape_manual(values=c(15,13,11,1,28),guide="none")+
  geom_point(aes(starshape=clade),color="dark grey",fill="light grey")+
  geom_line(aes(y=.fitted), color = "#3366FF",size=1)+
  geom_line(aes(y=.lower), color = "#3366FF", linetype = "dashed",alpha=0.5)+
  geom_line(aes(y=.upper), color = "#3366FF", linetype = "dashed",alpha=0.5)+
  geom_errorbar(data=.%>%filter(!!fossil.specimens,genus=="Dunkleosteus"),
                aes(ymin=.lower,ymax=.upper),width=3)+
  geom_point(data=.%>%filter(!!fossil.specimens,genus=="Dunkleosteus"),
             aes(starshape=clade,y=.fitted),fill="black",size=2)+
  labs(x="Orbit-Opercular Length (cm)",y="Total Length (cm)")+
  theme_classic()+
  theme(legend.position=c(0.8,0.25))+
  geom_label(data=.%>%filter(!!fossil.specimens,genus=="Dunkleosteus"),
             aes(y=ifelse(specimen=="CMNH 6090",.upper-4,
                          ifelse(specimen=="CMNH 7054",.upper+2,.upper)),
                 x=0,label=round(.upper,1)),size=3)+
  geom_label(data=.%>%filter(!!fossil.specimens,genus=="Dunkleosteus"),
             aes(y=ifelse(specimen=="CMNH 6090",.lower-8,
                 ifelse(specimen=="CMNH 7054",.lower+8,.lower)),
                 x=0,label=round(.lower,1)),size=3)+
  annotate("text",x=90,y=475,label="4.6 m",color="red")+
  geom_text(data=.%>%filter(!!fossil.specimens,genus=="Dunkleosteus"),
            aes(label=specimen,
                x=ifelse(specimen=="CMNH 7054",OOL+1,OOL),
                y=ifelse(specimen=="CMNH 6090",.lower-18,.lower)),hjust=0,nudge_y=-13)
Length estimates for Dunkleosteus terrelli plotted on a non-log transformed scale.

Figure 9.1: Length estimates for Dunkleosteus terrelli plotted on a non-log transformed scale.

Note that in order to produce lengths of greater than 5 m for large, adult individuals of Dunkleosteus terrelli (i.e., the very uppermost part of the prediction interval in this graph) requires Dunkleosteus to plot in a region of the graph where there are few fishes in general. Most of the fishes that are in this region of the graph are either anguilliform taxa or ones with highly elongate caudal fins such as Alopias. Indeed, most of the large fishes in the upper region of this graph tend to be those with highly elongate bodies such as Tetrapturus.

10 Estimating total length in Dunkleosteus terrelli under different models

For this section, length estimates will primarily focus on four specimens: CMNH 7424, CMNH 6090, CMNH 7054, and CMNH 5768. OOL is measurable in a number of other specimens of Dunkleosteus terrelli, but the four specimens listed here are known from complete, mounted heads that pertain to a single individual, and thus are the most useful as a reality check to test the effects of different models on the size of D. terrelli.

10.1 Predicting using fusiform taxa only

Note, for this model the families Serranidae, Monacanthidae, and Balistidae were excluded, due to these groups showing apomorphic specializations in skull morphology (either an enlarged head in Serranidae or a posteriorly shifted orbit in Balistoidei) which suggests they violate the assumptions of consistent head-body proportions seen in all other fusiform fishes.

fit.fusiform<-lm(log(total_length)~log(OOL),
                 data_final%>%
                   filter(shape %in% c("fusiform"),length_as!="estimated t.l.",
                          !family %in% c("Serranidae","Holocentridae",
                                         "Monacanthidae","Balistidae","")))

summary(fit.fusiform)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL), data = data_final %>% 
##     filter(shape %in% c("fusiform"), length_as != "estimated t.l.", 
##         !family %in% c("Serranidae", "Holocentridae", "Monacanthidae", 
##             "Balistidae", "")))
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.47860 -0.09612  0.00455  0.09063  0.78554 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 1.912061   0.007449   256.7   <2e-16 ***
## log(OOL)    0.971318   0.003296   294.7   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1543 on 1739 degrees of freedom
##   (4 observations deleted due to missingness)
## Multiple R-squared:  0.9804, Adjusted R-squared:  0.9804 
## F-statistic: 8.687e+04 on 1 and 1739 DF,  p-value: < 2.2e-16
regression.stats(fit.fusiform)
predict.fusiform<-fossil_taxa%>%
  drop_na(OOL)%>%
  filter(!!fossil.specimens)%>%
  augment(fit.fusiform,newdata=.,interval="prediction")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.fusiform)$CF),
         PE=(.fitted-total_length)/.fitted,
         .PE=paste0("(",round(.fitted*(1-
                            regression.stats(fit.fusiform)$adjPE/100),1),
                    "–",round(.fitted*(1+
                            regression.stats(fit.fusiform)$adjPE/100),1),")"),
         .range=paste0("(",round(.lower,1),
                       "–",round(.upper,1),
                       ")"))%>%
  arrange(.fitted)%>%
  select(taxon,specimen,total_length,.fitted,.PE,.range,PE)
predict.fusiform%>%
  kable(digits=c(1,1,1,1,1,1,3),
        caption= "Length estimates for specimens of <i>Dunkleosteus terrelli</i> using a model based on only fusiform fishes",
        col.names = c("Taxon","Specimen","Actual Length","Est.","+/- %PE","95% P.I.","%PE"))%>%
  column_spec(1, italic = T)%>%
  column_spec(4, bold = T)%>%
  kable_styling()
Table 10.1: Length estimates for specimens of Dunkleosteus terrelli using a model based on only fusiform fishes
Taxon Specimen Actual Length Est. +/- %PE 95% P.I. %PE
Millerosteus minor FMNH PF 1089 13.7 13.6 (12–15.3) (10.1–18.5) -0.006
Millerosteus minor Composite Millerosteus 14.9 15.7 (13.8–17.6) (11.6–21.3) 0.048
Africanaspis dorissa Gess and Trinajstic 2017 23.0 23.7 (20.9–26.5) (17.5–32.1) 0.029
Incisoscutum ritchei Recon. (Trinajstic 2013) 30.3 30.4 (26.8–34.1) (22.5–41.2) 0.006
Coccosteus cuspidatus NMS 1893.107.27 29.6 33.7 (29.7–37.7) (24.9–45.6) 0.121
Coccosteus cuspidatus NMS 1897.55.6 32.3 34.6 (30.5–38.7) (25.6–46.8) 0.067
Coccosteus cuspidatus FMNH PF 1673 37.1 35.0 (30.9–39.2) (25.9–47.4) -0.060
Coccosteus cuspidatus ROM VP 52664 37.5 40.6 (35.8–45.5) (30–55) 0.077
Coccosteus cuspidatus NMS 1900.12.12 34.4 41.2 (36.3–46.1) (30.4–55.8) 0.165
Coccosteus cuspidatus Recon. (M & W 1968) 39.4 42.0 (37–46.9) (31–56.8) 0.061
Holonema westolli Recon. (Miles 1971) 60.6 48.7 (42.9–54.5) (36–65.9) -0.245
Plourdosteus canadensis MNHM 2-177 37.5 48.9 (43.1–54.7) (36.1–66.2) 0.233
Dickosteus threiplandi NMS 1987.7.118 43.7 53.3 (46.9–59.6) (39.4–72.1) 0.180
Watsonosteus fletti NMS G.1995.4.2 56.6 61.7 (54.4–69.1) (45.6–83.6) 0.083
Gen. et sp. nov. CMNH 50233 63.0 64.7 (57–72.4) (47.8–87.6) 0.026
Dickosteus threiplandi NHMUK PV P 49663 52.3 65.6 (57.8–73.4) (48.5–88.8) 0.202
Amazichthys trinajsticae AA.MEM.DS.8 89.7 73.4 (64.7–82.2) (54.3–99.4) -0.221
Dunkleosteus terrelli CMNH 7424 NA 179.6 (158.3–200.9) (132.7–243.1) NA
Dunkleosteus terrelli CMNH 6090 NA 266.9 (235.2–298.6) (197.1–361.3) NA
Dunkleosteus terrelli CMNH 7054 NA 278.2 (245.1–311.2) (205.5–376.6) NA
Dunkleosteus terrelli CMNH 5768 NA 319.7 (281.7–357.6) (236.1–432.8) NA
rbind("%PE (all specimens)"=predict.fusiform%>%
    drop_na(total_length)%>%
    mutate(PE=((.fitted-total_length)/.fitted)%>%round(4))%>%
    pull(PE)%>%abs()%>%mean(),
  "%PE (excluding unusual specimens)"=predict.fusiform %>%
    drop_na(total_length)%>%
    filter(!taxon %in% c("Amazichthys trinajsticae","Holonema westolli"),
           specimen!="NHMUK PV P 49663")%>%
    mutate(PE=((.fitted-total_length)/.fitted)%>%round(4))%>%
    pull(PE)%>%abs()%>%mean())%>%
  kable(digits=4,col.names="PE",align="c",
        caption="Mean percent error for length estimations of complete arthrodires using OOL and a model based solely on fusiform fishes",
        table.attr = "style='width:70%;'")%>%
  kable_styling()
Table 10.2: Mean percent error for length estimations of complete arthrodires using OOL and a model based solely on fusiform fishes
PE
%PE (all specimens) 0.1077
%PE (excluding unusual specimens) 0.0830

“Unusual specimens” in this case refers to Holonema westolli, Amazichthys trinajsticae, and one specimen of Dickosteus threiplandi (NHMUK PV P 49663).

  1. Although the reconstruction of H. westolli from Trinajstic (1999) is used here because of the limited data, it is not clear if the head-trunk proportions of this specimen are correct. One specimen that preserves a near-complete tail (WAM 96.6.111A) seems to measure approximately 21-23 cm posterior to the armor. However, it is unclear how this specimen scales with the anterior half of the animal, as the only plate preserved is a partial posteroventrolateral. Thus, it is possible this animal had a shorter post-thoracic region, which would bring it more in line with the proportions seen in other arthrodires.
  2. Amazichthys trinajsticae shows unusual head-body proportions compared to other arthrodires,, and hence it is possible that this specimen is an outlier compared to other members of the group.
  3. The head region of NMHUK PV P 49963 is poorly preserved and it may be that OOL is mis-measured in this specimen. The rough dimensions of the head can be somewhat defined but many of its features are distorted (e.g., lateral lines ), and it may be that the location of the cranio-thoracic joint is not accurately identified. This is supported by the fact that its head proportions differ greatly from the other specimen of D. threiplandi considered here (NMS 1987.7.118).

10.1.1 Graph of fusiform fishes only

ggplot(data_final%>%
         cbind(exp(predict(lm(log(total_length)~log(OOL),
                              data_final%>%
                                filter(shape == "fusiform",
                                       length_as!="estimated t.l.")),
                       data_final,interval="prediction")))%>%
         filter(shape=="fusiform",
                length_as!="estimated t.l."),
       aes(y=total_length,x=OOL))+
  geom_point(aes(color=clade,shape=clade))+
  geom_point(aes(fill=clade,shape=clade),size=2)+
  geom_smooth(method="lm",formula=y~x)+
  scale_shape_manual(values=c(21,24,22,23))+
  scale_color_manual(values=c("red","green",NA,NA),na.value=NA)+
  geom_point(data=data_final%>%filter(family=="Serranidae"),
             shape=21,size=2,fill="gray")+
  geom_point(data=data_final%>%filter(family %in% c("Monacanthidae","Balistidae")&
                                          shape=="fusiform"),
             shape=21,size=2,fill="white")+
  geom_smooth(data=data_final%>%filter(family=="Serranidae"),
             formula=y~x,method="lm",color="black")+
  geom_line(aes(y=lwr), color = "blue", linetype = "dashed")+
  geom_line(aes(y=upr), color = "blue", linetype = "dashed")+
  labs(x="Orbit-Opercular Length (cm)",y="Total Length (cm)",
       color="Clade",shape="Clade",fill="Clade")+
  scale_x_continuous(trans='log10')+
  scale_y_continuous(trans='log10')+
  theme_classic()+
  theme(legend.position=c(0.8,0.25))
Plot of orbit-opercular length against total length on a log10 scale in fusiform fishes. Groupers (Serranidae), which show a shift in the y-intercept of their regression line due to having disproportionately large heads, are denoted in gray. Balistoidei, which are also excluded because of an apomorphic posterior shift in the orbit, are denoted in white.

Figure 10.1: Plot of orbit-opercular length against total length on a log10 scale in fusiform fishes. Groupers (Serranidae), which show a shift in the y-intercept of their regression line due to having disproportionately large heads, are denoted in gray. Balistoidei, which are also excluded because of an apomorphic posterior shift in the orbit, are denoted in white.

ggplot(data_final%>%
         cbind(exp(predict(lm(log(total_length)~log(OOL),
                              data_final%>%
                                filter(shape == "fusiform",
                                       length_as!="estimated t.l.")),
                       data_final,interval="prediction")))%>%
         filter(shape=="fusiform",
                length_as!="estimated t.l."),
       aes(y=OOL/total_length,x=total_length))+
  geom_point(aes(color=clade,shape=clade))+
  geom_point(aes(fill=clade,shape=clade),size=2)+
  geom_smooth(method="lm",formula=y~x)+
  scale_shape_manual(values=c(21,24,22,23))+
  scale_color_manual(values=c("red","green",NA,NA),na.value=NA)+
  geom_point(data=data_final%>%filter(family=="Serranidae"),
             shape=21,size=2,fill="gray")+
  geom_point(data=data_final%>%filter(family %in% c("Monacanthidae","Balistidae")&
                                          shape=="fusiform"),
             shape=21,size=2,fill="white")+
  geom_smooth(data=data_final%>%filter(family=="Serranidae"),
             formula=y~x,method="lm",color="black")+
  geom_line(aes(y=lwr), color = "blue", linetype = "dashed")+
  geom_line(aes(y=upr), color = "blue", linetype = "dashed")+
  labs(x="Total Length (cm)",y="Percent OOL",
       color="Clade",shape="Clade",fill="Clade")+
  scale_x_continuous(trans='log10')+
  scale_y_continuous(trans='log10')+
  theme_classic()+
  theme(legend.position=c(0.8,0.25))

10.1.2 Effect of groupers (Serranidae)

10.1.2.1 OOL in groupers relative to other fishes

ggplot(data_final%>%
         drop_na(OOL,total_length)%>%
         filter(shape=="fusiform",
                length_as!="estimated t.l.")%>%
         mutate(clade=case_when(clade=="Placodermi"~"Placodermi",
                          clade=="Sarcopterygii"~"Sarcopterygii",
                          family %in% c("Lamnidae","Megachasmatidae")~"Lamnidae + Megachasmatidae",
                          clade=="Chondrichthyes"~"Chondrichthyes",
                          family=="Serranidae"~"Serranidae",
                          family %in% c("Monacanthidae","Balisidae")~"Balistoidei",
                          clade=="Actinopterygii"~"Actinopterygii, Other"))%>%
         arrange(clade),
       aes(y=OOL/total_length,x=total_length))+
  geom_star(aes(color=clade,starshape=clade))+
  geom_star(aes(fill=clade,starshape=clade),size=2)+
  geom_star(aes(fill=clade,starshape=clade),size=3,
            data=.%>% filter(clade=="Placodermi"))+
  geom_smooth(method="lm",formula=y~x)+
  scale_starshape_manual(values=c(15,15,13,13,1,15,15))+
  scale_fill_manual(values=c(hue_pal()(5)[1],"white",hue_pal()(5)[-1],"gray"))+
  geom_smooth(data=data_final%>%filter(family=="Serranidae"),
              formula=y~x,method="lm",color="black")+
  labs(x="Total Length (cm)",y="OOL (as percent of total length)",
       color="Clade",starshape="Clade",fill="Clade")+
  scale_x_continuous(trans='log10')+
  scale_y_continuous(labels=scales::label_percent(accuracy = 1))+
  theme_classic()
OOL graphed as a percentage of total length in fusiform fishes, showing how groupers (Serranidae), particularly more derived groupers of the Cephalopholis-Epinephelus clade and not more basal grouper like Variola and Plectropomus, have much larger OOL than would be expected for fusiform taxa, and taxa with posteriorly shifted orbits (Balistoidei) have much smaller OOL than expected.

Figure 10.2: OOL graphed as a percentage of total length in fusiform fishes, showing how groupers (Serranidae), particularly more derived groupers of the Cephalopholis-Epinephelus clade and not more basal grouper like Variola and Plectropomus, have much larger OOL than would be expected for fusiform taxa, and taxa with posteriorly shifted orbits (Balistoidei) have much smaller OOL than expected.

10.1.2.2 Testing if groupers show a significantly different intercept than other fishes

data_final%$%
  lm(log(total_length)~log(OOL)+(family=="Serranidae"),.)%>%
  summary()
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) + (family == "Serranidae"), 
##     data = .)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.64110 -0.13139 -0.00761  0.11900  1.53088 
## 
## Coefficients:
##                             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                 1.927582   0.008375  230.16   <2e-16 ***
## log(OOL)                    0.994479   0.003821  260.26   <2e-16 ***
## family == "Serranidae"TRUE -0.373931   0.015065  -24.82   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2057 on 3166 degrees of freedom
##   (53 observations deleted due to missingness)
## Multiple R-squared:  0.9559, Adjusted R-squared:  0.9559 
## F-statistic: 3.43e+04 on 2 and 3166 DF,  p-value: < 2.2e-16
data_final%$%
  lm(log(total_length)~log(head_length)+(family=="Serranidae"),.)%>%
  summary()
## 
## Call:
## lm(formula = log(total_length) ~ log(head_length) + (family == 
##     "Serranidae"), data = .)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.51915 -0.10999 -0.00499  0.09563  1.62999 
## 
## Coefficients:
##                             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                 1.592558   0.008167  195.01   <2e-16 ***
## log(head_length)            0.968756   0.003183  304.37   <2e-16 ***
## family == "Serranidae"TRUE -0.268291   0.012970  -20.68   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.177 on 3166 degrees of freedom
##   (53 observations deleted due to missingness)
## Multiple R-squared:  0.9674, Adjusted R-squared:  0.9673 
## F-statistic: 4.691e+04 on 2 and 3166 DF,  p-value: < 2.2e-16

Based on this, groupers have a significantly lower intercept (i.e., larger head/OOL relative to total length) than is typical for fishes.

10.1.2.3 Testing whether groupers have larger OOL than other fishes when body shape is accounted for

data_final%$%
  lm(log(total_length)~log(OOL)+shape+(family=="Serranidae"),.)%>%
  summary()
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) + shape + (family == 
##     "Serranidae"), data = .)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.64948 -0.09520 -0.00805  0.09211  1.38777 
## 
## Coefficients:
##                             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                 1.897877   0.007540 251.707   <2e-16 ***
## log(OOL)                    0.978394   0.003241 301.868   <2e-16 ***
## shapeanguilliform           0.215939   0.009091  23.754   <2e-16 ***
## shapecompressiform         -0.122066   0.011265 -10.835   <2e-16 ***
## shapeelongate               0.224477   0.009048  24.809   <2e-16 ***
## shapeflattened              0.049513   0.033767   1.466    0.143    
## shapemacruriform            0.405574   0.027649  14.669   <2e-16 ***
## family == "Serranidae"TRUE -0.310218   0.012729 -24.372   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1706 on 3161 degrees of freedom
##   (53 observations deleted due to missingness)
## Multiple R-squared:  0.9697, Adjusted R-squared:  0.9696 
## F-statistic: 1.445e+04 on 7 and 3161 DF,  p-value: < 2.2e-16

Even if accounting for shape, grouper still stand out as a major outlier.

10.1.2.4 Summary of slopes test between groupers and other fusiform fishes

data_final %>%
  filter(shape=="fusiform") %$%
  lm(log(total_length)~log(OOL)*(family=="Serranidae"),.)%>%
  summary()
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) * (family == "Serranidae"), 
##     data = .)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.47883 -0.09856  0.00122  0.08809  0.93848 
## 
## Coefficients:
##                                      Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                          1.912362   0.007491 255.295  < 2e-16 ***
## log(OOL)                             0.971215   0.003319 292.625  < 2e-16 ***
## family == "Serranidae"TRUE          -0.320537   0.043750  -7.327 3.45e-13 ***
## log(OOL):family == "Serranidae"TRUE  0.002961   0.022289   0.133    0.894    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1555 on 1938 degrees of freedom
##   (44 observations deleted due to missingness)
## Multiple R-squared:  0.9786, Adjusted R-squared:  0.9786 
## F-statistic: 2.955e+04 on 3 and 1938 DF,  p-value: < 2.2e-16

Based on this, it appears as though despite serranids showing a much significantly lower intercept (i.e., larger head/OOL) than other fusiform fishes, the allometric relationship (slope) between the two is the same.

10.1.2.5 Predicting body lengths in arthrodires using only groupers

fit.grouper<-lm(log(total_length)~log(OOL),
                data_final%>%filter(family=="Serranidae"))
(predict(fit.grouper,
        fossil_taxa%>%
          filter(!is.na(OOL),clade=="Placodermi",
                 genus=="Dunkleosteus"&specimen%in% c("CMNH 7054","CMNH 6090","CMNH 5768","CMNH 7424")|length_as=="total length"),
        interval="prediction")%>%
  exp()*regression.stats(fit.grouper)$CF)%>%
  data.frame()%>%rownames_to_column()%>%
  arrange(fit)%>%
  left_join(fossil_taxa%>%rownames_to_column(),by="rowname")%>%
  mutate(est_OOL=exp(predict(fit.fusiform,.))*regression.stats(fit.fusiform)$CF,
         total_length=ifelse(length_as=="estimated t.l.",NA,total_length))%>%
  select(taxon,specimen,total_length,est_OOL,fit,lwr,upr)%>%
  kable(digits=1,align=c("l","c","c","c","c","c","c"),
        caption= "Length estimates for specimens of <i>Dunkleosteus terrelli</i> using a model based on only groupers (Serranidae)",
        col.names = c("Taxon","Specimen","Actual Length","Estimated Length Using All Fusiform Taxa","Est.","Lwr 95% P.I.","Upr 95% P.I."))%>%
  column_spec(1, italic = T)%>%
  column_spec(5, bold = T)%>%
  add_header_above(c(" "=4,"Estimated Using Serranidae"=3))%>%
  kable_styling()
Table 10.3: Length estimates for specimens of Dunkleosteus terrelli using a model based on only groupers (Serranidae)
Estimated Using Serranidae
Taxon Specimen Actual Length Estimated Length Using All Fusiform Taxa Est. Lwr 95% P.I. Upr 95% P.I.
Millerosteus minor FMNH PF 1089 13.7 13.6 9.9 7.6 12.9
Millerosteus minor Composite Millerosteus 14.9 15.7 11.4 8.8 14.9
Africanaspis dorissa Gess and Trinajstic 2017 23.0 23.7 17.3 13.3 22.5
Incisoscutum ritchei Recon. (Trinajstic 2013) 30.3 30.4 22.2 17.1 28.9
Coccosteus cuspidatus NMS 1893.107.27 29.6 33.7 24.6 18.9 32.0
Coccosteus cuspidatus NMS 1897.55.6 32.3 34.6 25.3 19.4 32.9
Coccosteus cuspidatus FMNH PF 1673 37.1 35.0 25.6 19.7 33.3
Coccosteus cuspidatus ROM VP 52664 37.5 40.6 29.7 22.8 38.7
Coccosteus cuspidatus NMS 1900.12.12 34.4 41.2 30.1 23.2 39.2
Coccosteus cuspidatus Recon. (M & W 1968) 39.4 42.0 30.7 23.6 39.9
Holonema westolli Recon. (Miles 1971) 60.6 48.7 35.6 27.4 46.4
Plourdosteus canadensis MNHM 2-177 37.5 48.9 35.8 27.5 46.6
Dickosteus threiplandi NMS 1987.7.118 43.7 53.3 39.0 30.0 50.8
Watsonosteus fletti NMS G.1995.4.2 56.6 61.7 45.3 34.8 58.9
Gen. et sp. nov. CMNH 50233 63.0 64.7 47.4 36.5 61.7
Dickosteus threiplandi NHMUK PV P 49663 52.3 65.6 48.1 37.0 62.6
Amazichthys trinajsticae AA.MEM.DS.8 89.7 73.4 53.9 41.4 70.2
Dunkleosteus terrelli CMNH 7424 NA 179.6 132.6 101.4 173.5
Dunkleosteus terrelli CMNH 6090 NA 266.9 197.6 150.5 259.3
Dunkleosteus terrelli CMNH 7054 NA 278.2 206.0 156.9 270.5
Dunkleosteus terrelli CMNH 5768 NA 319.7 236.9 180.2 311.5

As can be seen by these results, using groupers (Serranidae) results in much shorter lengths for arthrodires, specifically because serranids have disproportionately large heads relative to their body size compared to other fishes. Thus, although groupers have a largely fusiform body plan, it seems reasonable to exclude them from the fusiform fishes-only model because of their unusual proportions.

10.1.3 Plotting estimated lengths for Dunkleosteus using a fusiform-only model

ggplot(data_final%>%
         augment(fit.fusiform,newdata=.,interval="predict")%>%
         mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.fusiform)$CF))%>%
         filter(shape=="fusiform"),
       aes(y=total_length,x=OOL))+
    geom_star(aes(starshape=clade),color="dark grey",fill="light grey")+
    stat_smooth(geom='line',aes(y=.fitted), color = "#3366FF", method="lm", 
                alpha=0.95, se=FALSE,size=.75,formula=y~x)+
    geom_line(aes(y=.lower), color = "#3366FF", linetype = "dashed",alpha=0.5)+
    geom_line(aes(y=.upper), color = "#3366FF", linetype = "dashed",alpha=0.5)+
    geom_errorbar(data=.%>%filter(!!fossil.specimens,genus=="Dunkleosteus"),
                  aes(ymin=.lower,ymax=.upper),width=0.1)+
    geom_star(data=.%>%filter(!!fossil.specimens,genus=="Dunkleosteus"),
               aes(starshape=clade,y=.fitted),fill="black",size=2)+
    scale_starshape_manual(values=c(15,11,13,1,28),guide="none")+
    labs(x="Orbit-Opercular Length (cm)",y="Total Length (cm)")+
  coord_cartesian(y=c(0,500),x=c(0,75))+
  geom_segment(data=.%>%filter(genus=="Dunkleosteus",!!fossil.specimens),
               aes(y=.lower,yend=.lower,x=-20,xend=OOL),
               linetype="dashed",alpha=0.75)+
  geom_segment(data=.%>%filter(genus=="Dunkleosteus",!!fossil.specimens),
               aes(y=.upper,yend=.upper,x=-20,xend=OOL),
               linetype="dashed",alpha=0.75)+
  geom_errorbar(data=.%>%filter(!!fossil.specimens,genus=="Dunkleosteus"),
                aes(ymin=.lower,ymax=.upper),width=3)+
  geom_label(data=.%>%filter(!!fossil.specimens,genus=="Dunkleosteus"),
             aes(y=.upper,x=0,label=round(.upper,1)),size=3)+
  geom_label(data=.%>%filter(!!fossil.specimens,genus=="Dunkleosteus"),
             aes(y=.lower,x=0,label=round(.lower,1)),size=3)+
  geom_text(data=.%>%filter(!!fossil.specimens,genus=="Dunkleosteus"),
            aes(label=specimen,x=OOL,
                y=ifelse(specimen=="CMNH 6090",.lower-10,.lower)),hjust=0,nudge_y=-13)+
  theme_classic()
Minimum and maximum estimated length in specimens of Dunkleosteus terrelli considered in this study using orbit-opercular length, using an equation only considering fusiform taxa (i.e., excluding taxa with very elongate body shapes like barracudas). Plot is zoomed in to focus on lengths in Dunkleosteus. Error bars represent 95% prediction intervals.

Figure 10.3: Minimum and maximum estimated length in specimens of Dunkleosteus terrelli considered in this study using orbit-opercular length, using an equation only considering fusiform taxa (i.e., excluding taxa with very elongate body shapes like barracudas). Plot is zoomed in to focus on lengths in Dunkleosteus. Error bars represent 95% prediction intervals.

10.2 Predicting using pelagic taxa

10.2.1 Model only considering pelagic taxa

fit.pelagic<-lm(log(total_length)~log(OOL),
                data_final%>%
                 filter(!shape %in% c("macruriform","anguilliform"),
                        habitat %in% c("pelagic"),
                        !order %in% c("Rajiformes","Chimaeriformes"),
                        family !="Molidae",
                        !genus %in% c("Alopias","Regalecus"),
                        length_as!="estimated t.l."))

summary(fit.pelagic)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL), data = data_final %>% 
##     filter(!shape %in% c("macruriform", "anguilliform"), habitat %in% 
##         c("pelagic"), !order %in% c("Rajiformes", "Chimaeriformes"), 
##         family != "Molidae", !genus %in% c("Alopias", "Regalecus"), 
##         length_as != "estimated t.l."))
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.46661 -0.14354 -0.00813  0.16128  0.71921 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 2.037299   0.024742   82.34   <2e-16 ***
## log(OOL)    0.967703   0.008509  113.73   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1974 on 636 degrees of freedom
##   (9 observations deleted due to missingness)
## Multiple R-squared:  0.9531, Adjusted R-squared:  0.9531 
## F-statistic: 1.293e+04 on 1 and 636 DF,  p-value: < 2.2e-16
regression.stats(fit.pelagic)

10.2.2 Model using habitat as an additional categorical variable

Note: Regalecus and Alopias were excluded from this model given their highly unusual body shapes compared to typical pelagic fishes.

fit.pelagic_categorical<-lm(log(total_length)~log(OOL)+habitat+
                              (family=="Serranidae"),
                data_final%>%
                 filter(!shape %in% c("macruriform","anguilliform"),
                        !order %in% c("Rajiformes","Chimaeriformes"),
                        !genus %in% c("Alopias","Regalecus"),
                        length_as!="estimated t.l."))
summary(fit.pelagic_categorical)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) + habitat + (family == 
##     "Serranidae"), data = data_final %>% filter(!shape %in% c("macruriform", 
##     "anguilliform"), !order %in% c("Rajiformes", "Chimaeriformes"), 
##     !genus %in% c("Alopias", "Regalecus"), length_as != "estimated t.l."))
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.64809 -0.12248 -0.00544  0.11633  1.07584 
## 
## Coefficients:
##                             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                 1.890826   0.008688 217.628  < 2e-16 ***
## log(OOL)                    0.974702   0.004006 243.290  < 2e-16 ***
## habitatbenthic              0.044706   0.014212   3.146  0.00168 ** 
## habitatneritic              0.034554   0.010251   3.371  0.00076 ***
## habitatpelagic              0.126054   0.010192  12.368  < 2e-16 ***
## family == "Serranidae"TRUE -0.301080   0.014390 -20.923  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1883 on 2683 degrees of freedom
##   (14 observations deleted due to missingness)
## Multiple R-squared:  0.9672, Adjusted R-squared:  0.9671 
## F-statistic: 1.58e+04 on 5 and 2683 DF,  p-value: < 2.2e-16
regression.stats(fit.pelagic_categorical)
fit.pelagic_categorical_species_averages<-
  lm(log(total_length)~log(OOL)+habitat+(family=="Serranidae"),
                data_species %>%
                 filter(!shape %in% c("macruriform","anguilliform"),
                        !genus %in% c("Alopias","Regalecus"),
                        !order %in% c("Rajiformes","Chimaeriformes")))
summary(fit.pelagic_categorical_species_averages)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) + habitat + (family == 
##     "Serranidae"), data = data_species %>% filter(!shape %in% 
##     c("macruriform", "anguilliform"), !genus %in% c("Alopias", 
##     "Regalecus"), !order %in% c("Rajiformes", "Chimaeriformes")))
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.59491 -0.11176 -0.00376  0.09799  1.06055 
## 
## Coefficients:
##                             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                 1.908923   0.014559 131.114  < 2e-16 ***
## log(OOL)                    0.971019   0.007069 137.370  < 2e-16 ***
## habitatbenthic              0.036828   0.022498   1.637 0.101986    
## habitatneritic              0.029849   0.016182   1.845 0.065414 .  
## habitatpelagic              0.072419   0.019422   3.729 0.000204 ***
## family == "Serranidae"TRUE -0.282089   0.025060 -11.256  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1877 on 905 degrees of freedom
##   (4 observations deleted due to missingness)
## Multiple R-squared:  0.9575, Adjusted R-squared:  0.9573 
## F-statistic:  4079 on 5 and 905 DF,  p-value: < 2.2e-16
regression.stats(fit.pelagic_categorical_species_averages)

10.2.3 Predictions for Dunkleosteus

fit.pelagic_averages<-lm(log(total_length)~log(OOL),data_species%>%
                                   filter(habitat=="pelagic",
                                          !shape %in% c("macruriform","anguilliform"),
                                          !genus %in% c("Alopias","Regalecus"),
                                          !family %in% c("Molidae"),
                                          !order %in% c("Rajiformes","Chimaeriformes")))

fossil_taxa%>%
  filter(!!fossil.specimens)%>%drop_na(habitat)%>%
  augment(fit.pelagic,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.pelagic)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit1",.)))%>%
  augment(fit.pelagic_averages,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.pelagic_averages)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit2",.)))%>%
  augment(fit.pelagic_categorical,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.pelagic_categorical)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit3",.)))%>%
  augment(fit.pelagic_categorical_species_averages,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.pelagic_categorical_species_averages)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit4",.)))%>%
  mutate(fit1.PE=paste0("(",round(fit1.fitted*(1-
                            regression.stats(fit.pelagic)$adjPE/100),1),
                    "–",round(fit1.fitted*(1+
                            regression.stats(fit.pelagic)$adjPE/100),1),")"),
         fit2.PE=paste0("(",round(fit2.fitted*(1-
                            regression.stats(fit.pelagic_averages)$adjPE/100),1),
                    "–",round(fit2.fitted*(1+
                            regression.stats(fit.pelagic_averages)$adjPE/100),1),")"),
         fit3.PE=paste0("(",round(fit1.fitted*(1-
                            regression.stats(fit.pelagic_categorical)$adjPE/100),1),
                    "–",round(fit1.fitted*(1+
                            regression.stats(fit.pelagic_categorical)$adjPE/100),1),")"),
         fit4.PE=paste0("(",round(fit4.fitted*(1-
                            regression.stats(fit.pelagic_categorical_species_averages)$adjPE/100),1),
                    "–",round(fit4.fitted*(1+
                            regression.stats(fit.pelagic_categorical_species_averages)$adjPE/100),1),")"),
         fit1.range=paste0("(",round(fit1.lower,1),"–",round(fit1.upper,1),")"),
         fit2.range=paste0("(",round(fit2.lower,1),"–",round(fit2.upper,1),")"),
         fit3.range=paste0("(",round(fit3.lower,1),"–",round(fit3.upper,1),")"),
         fit4.range=paste0("(",round(fit4.lower,1),"–",round(fit4.upper,1),")"))%>%
  select(taxon,specimen,total_length,
         fit1.fitted,fit1.PE,fit1.range,
         fit2.fitted,fit2.PE,fit2.range,
         fit3.fitted,fit3.PE,fit3.range,
         fit4.fitted,fit4.PE,fit4.range,)%>%
  kable(digits=1,
        caption= "Length estimates for specimens of <i>Dunkleosteus terrelli</i> using a model considering only pelagic taxa, excluding rays, molids, and anguilliform (e.g., <i>Regalecus</i> and macruriform (e.g., <i>Alopias</i>) taxa",
        col.names = c("Taxon","Specimen","Total Length",
                      "Est.","+/- %PE","95% P.I.",
                      "Est.","+/- %PE","95% P.I.",
                      "Est.","+/- %PE","95% P.I.",
                      "Est.","+/- %PE","95% P.I."))%>%
  column_spec(1, italic = T)%>%
  column_spec(c(4,7,10,13), bold = T)%>%
  add_header_above(c(" "=3,"Individual Specimens"=3,"Species Averages"=3,
                     "Individual Specimens"=3,"Species Averages"=3))%>%
  add_header_above(c(" "=3,"Considering Pelagic Taxa Only"=6,
                     "Treating Life Habits as Additional Categorical Variable"=6))%>%
  kable_styling()%>%
  scroll_box(width = "100%")
Table 10.4: Length estimates for specimens of Dunkleosteus terrelli using a model considering only pelagic taxa, excluding rays, molids, and anguilliform (e.g., Regalecus and macruriform (e.g., Alopias) taxa
Considering Pelagic Taxa Only
Treating Life Habits as Additional Categorical Variable
Individual Specimens
Species Averages
Individual Specimens
Species Averages
Taxon Specimen Total Length Est. +/- %PE 95% P.I. Est. +/- %PE 95% P.I. Est. +/- %PE 95% P.I. Est. +/- %PE 95% P.I.
Millerosteus minor FMNH PF 1089 13.7 15.4 (12.9–18) (10.5–22.8) 15.3 (12.8–17.7) (10.3–22.5) 14.0 (13.1–17.7) (9.7–20.3) 14.2 (12.1–16.2) (9.8–20.5)
Millerosteus minor Composite Millerosteus 14.9 17.8 (14.8–20.7) (12–26.2) 17.5 (14.7–20.3) (11.9–25.9) 16.2 (15.1–20.4) (11.2–23.4) 16.3 (14–18.6) (11.3–23.6)
Africanaspis dorissa Gess and Trinajstic 2017 23.0 26.8 (22.3–31.2) (18.1–39.5) 26.1 (22–30.3) (17.7–38.5) 23.3 (22.8–30.7) (16.1–33.8) 23.7 (20.3–27.1) (16.4–34.3)
Incisoscutum ritchei Recon. (Trinajstic 2013) 30.3 34.4 (28.7–40) (23.3–50.7) 33.4 (28.1–38.6) (22.6–49.1) 30.0 (29.2–39.5) (20.8–43.4) 30.5 (26.1–34.8) (21.1–44)
Coccosteus cuspidatus NMS 1893.107.27 29.6 38.0 (31.7–44.3) (25.8–56.1) 36.8 (31–42.7) (25–54.2) 33.2 (32.4–43.7) (23–48.1) 33.7 (28.9–38.5) (23.3–48.7)
Coccosteus cuspidatus NMS 1897.55.6 32.3 39.0 (32.5–45.5) (26.5–57.5) 37.8 (31.8–43.7) (25.7–55.6) 34.1 (33.2–44.8) (23.6–49.4) 34.6 (29.7–39.5) (23.9–50)
Coccosteus cuspidatus FMNH PF 1673 37.1 39.5 (33–46) (26.8–58.2) 38.2 (32.2–44.3) (26–56.3) 34.6 (33.6–45.4) (23.9–50) 35.0 (30.1–40) (24.2–50.7)
Coccosteus cuspidatus Recon. (M & W 1968) 39.4 47.3 (39.5–55.1) (32.1–69.7) 45.6 (38.4–52.8) (31–67.1) 41.4 (40.3–54.3) (28.6–59.9) 42.0 (36–47.9) (29–60.7)
Coccosteus cuspidatus NMS 1900.12.12 34.4 46.4 (38.7–54.1) (31.5–68.5) 44.8 (37.7–51.9) (30.4–65.9) 40.7 (39.5–53.3) (28.1–58.8) 41.2 (35.4–47.1) (28.5–59.6)
Coccosteus cuspidatus ROM VP 52664 37.5 45.8 (38.2–53.4) (31.1–67.5) 44.2 (37.2–51.2) (30–65) 40.1 (39–52.6) (27.7–58) 40.6 (34.9–46.4) (28.1–58.8)
Plourdosteus canadensis MNHM 2-177 37.5 55.1 (45.9–64.2) (37.4–81.2) 52.9 (44.5–61.3) (35.9–77.8) 48.3 (46.9–63.2) (33.4–69.9) 48.9 (41.9–55.9) (33.8–70.7)
Dickosteus threiplandi NMS 1987.7.118 43.7 60.0 (50–69.9) (40.7–88.4) 57.5 (48.4–66.6) (39.1–84.6) 52.6 (51.1–68.9) (36.4–76.1) 53.3 (45.7–60.9) (36.9–77)
Holonema westolli Recon. (Miles 1971) 60.6 54.8 (45.8–63.9) (37.2–80.8) 52.7 (44.3–61) (35.8–77.5) 50.3 (46.7–63) (34.7–72.8) 50.5 (43.3–57.7) (34.9–73.2)
Watsonosteus fletti NMS G.1995.4.2 56.6 69.5 (58–81) (47.1–102.4) 66.4 (55.9–76.9) (45.1–97.6) 61.0 (59.2–79.8) (42.2–88.3) 61.8 (53–70.5) (42.7–89.3)
Gen. et sp. nov. CMNH 50233 63.0 72.8 (60.7–84.8) (49.4–107.3) 69.4 (58.4–80.4) (47.2–102.2) 72.5 (62–83.6) (50.1–105) 69.6 (59.7–79.4) (48.1–100.7)
Dickosteus threiplandi NHMUK PV P 49663 52.3 73.8 (61.6–86) (50.1–108.8) 70.4 (59.2–81.5) (47.8–103.6) 64.8 (62.8–84.7) (44.8–93.8) 65.6 (56.3–74.9) (45.4–94.9)
Amazichthys trinajsticae AA.MEM.DS.8 89.7 82.6 (68.9–96.3) (56–121.7) 78.6 (66.1–91) (53.4–115.6) 82.4 (70.3–94.9) (57–119.2) 79.0 (67.7–90.2) (54.6–114.3)
Dunkleosteus terrelli CMNH 7424 NA 201.3 (168–234.6) (136.6–296.7) 187.6 (157.9–217.3) (127.3–276.6) 202.1 (171.4–231.2) (139.7–292.5) 193.1 (165.6–220.5) (133.3–279.5)
Dunkleosteus terrelli CMNH 6090 NA 298.7 (249.2–348.1) (202.6–440.4) 275.8 (232.2–319.5) (186.8–407.4) 300.8 (254.3–343) (207.9–435.2) 286.8 (246.1–327.6) (198.1–415.4)
Dunkleosteus terrelli CMNH 7054 NA 311.3 (259.7–362.8) (211.1–459) 287.2 (241.7–332.7) (194.4–424.2) 313.6 (265–357.5) (216.7–453.7) 299.0 (256.5–341.5) (206.4–433)
Dunkleosteus terrelli CMNH 5768 NA 357.5 (298.3–416.7) (242.4–527.2) 328.8 (276.7–380.9) (222.4–486.1) 360.5 (304.4–410.6) (249.1–521.6) 343.5 (294.7–392.4) (237.2–497.6)

10.2.4 Predicting using pelagic and neritic taxa

fit.pelagic_neritic<-lm(log(total_length)~log(OOL),
                data_final%>%
                 filter(habitat %in% c("pelagic","neritic"),
                        !shape %in% c("anguilliform","macruriform"),
                        !order %in% c("Rajiformes","Chimaeriformes"),
                        family != "Molidae",
                        genus!="Alopias",
                        length_as!="estimated t.l."))

fit.pelagic_neritic_averages<-lm(log(total_length)~log(OOL),
                data_species%>%
                 filter(habitat %in% c("pelagic","neritic"),
                        !shape %in% c("anguilliform","macruriform"),
                        !order %in% c("Rajiformes","Chimaeriformes"),
                        family != "Molidae",
                        genus!="Alopias"))

summary(fit.pelagic_neritic)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL), data = data_final %>% 
##     filter(habitat %in% c("pelagic", "neritic"), !shape %in% 
##         c("anguilliform", "macruriform"), !order %in% c("Rajiformes", 
##         "Chimaeriformes"), family != "Molidae", genus != "Alopias", 
##         length_as != "estimated t.l."))
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.53511 -0.13147 -0.03248  0.13202  0.87878 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 1.954422   0.014432   135.4   <2e-16 ***
## log(OOL)    0.984589   0.005609   175.5   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1897 on 1108 degrees of freedom
##   (10 observations deleted due to missingness)
## Multiple R-squared:  0.9653, Adjusted R-squared:  0.9653 
## F-statistic: 3.081e+04 on 1 and 1108 DF,  p-value: < 2.2e-16
regression.stats(fit.pelagic_neritic)
fossil_taxa%>%
  filter(!!fossil.specimens)%>%
  augment(fit.pelagic_neritic_averages,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.pelagic_neritic)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit1",.)))%>%
  augment(fit.pelagic_neritic_averages,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.pelagic_neritic_averages)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit2",.)))%>%
  mutate(fit1.range=paste0("(",round(fit1.lower,1),"–",round(fit1.upper,1),")"),
         fit2.range=paste0("(",round(fit2.lower,1),"–",round(fit2.upper,1),")"),
         fit1.PE=paste0("(",round(fit1.fitted*(1-
                            regression.stats(fit.pelagic_neritic)$adjPE/100),1),
                    "–",round(fit1.fitted*(1+
                            regression.stats(fit.pelagic_neritic)$adjPE/100),1),")"),
         fit2.PE=paste0("(",round(fit2.fitted*(1-
                            regression.stats(fit.pelagic_neritic_averages)$adjPE/100),1),
                    "–",round(fit2.fitted*(1+
                            regression.stats(fit.pelagic_neritic_averages)$adjPE/100),1),")"))%>%
  select(taxon,specimen,fit1.fitted,fit1.PE,fit1.range,fit2.fitted,fit2.PE,fit2.range)%>%
  kable(digits=1,
        caption= "Length estimates for specimens of <i>Dunkleosteus terrelli</i> using a model considering pelagic and neritic taxa, excluding rays, molids, and anguilliform (e.g., <i>Regalecus</i> and macruriform (e.g., <i>Alopias</i>) taxa",
        col.names = c("Taxon","Specimen",
                      "Est.","+/- PE","95% P.I.",
                      "Est.","+/- PE","95% P.I."))%>%
  add_header_above(c(" "=2,"Individual Specimens"=3,"Species Averages"=3))%>%
  column_spec(1, italic = T)%>%
  column_spec(c(3,6), bold = T)%>%
  kable_styling()
Table 10.5: Length estimates for specimens of Dunkleosteus terrelli using a model considering pelagic and neritic taxa, excluding rays, molids, and anguilliform (e.g., Regalecus and macruriform (e.g., Alopias) taxa
Individual Specimens
Species Averages
Taxon Specimen Est. +/- PE 95% P.I. Est. +/- PE 95% P.I.
Millerosteus minor FMNH PF 1089 14.4 (12.2–16.7) (10.1–20.6) 14.4 (12.4–16.5) (10.1–20.6)
Millerosteus minor Composite Millerosteus 16.6 (14–19.2) (11.6–23.7) 16.6 (14.3–19) (11.6–23.7)
Africanaspis dorissa Gess and Trinajstic 2017 24.9 (21–28.9) (17.5–35.6) 24.9 (21.4–28.5) (17.5–35.6)
Incisoscutum ritchei Recon. (Trinajstic 2013) 32.0 (27–37) (22.4–45.7) 32.0 (27.4–36.5) (22.4–45.7)
Coccosteus cuspidatus NMS 1893.107.27 35.4 (29.8–40.9) (24.7–50.5) 35.4 (30.3–40.4) (24.7–50.5)
Coccosteus cuspidatus NMS 1897.55.6 36.3 (30.6–42) (25.4–51.8) 36.3 (31.1–41.4) (25.4–51.8)
Coccosteus cuspidatus FMNH PF 1673 36.7 (31–42.5) (25.7–52.5) 36.7 (31.5–41.9) (25.7–52.5)
Coccosteus cuspidatus Recon. (M & W 1968) 43.9 (37–50.8) (30.7–62.7) 43.9 (37.7–50.1) (30.7–62.7)
Coccosteus cuspidatus NMS 1900.12.12 43.1 (36.4–49.9) (30.2–61.6) 43.1 (37–49.2) (30.2–61.6)
Coccosteus cuspidatus ROM VP 52664 42.5 (35.9–49.2) (29.8–60.8) 42.5 (36.5–48.6) (29.8–60.8)
Plourdosteus canadensis MNHM 2-177 51.1 (43.1–59.1) (35.8–73) 51.1 (43.9–58.3) (35.8–73)
Dickosteus threiplandi NMS 1987.7.118 55.6 (46.9–64.4) (38.9–79.5) 55.6 (47.8–63.5) (38.9–79.5)
Holonema westolli Recon. (Miles 1971) 50.9 (42.9–58.9) (35.6–72.7) 50.9 (43.7–58.1) (35.6–72.7)
Watsonosteus fletti NMS G.1995.4.2 64.4 (54.3–74.5) (45.1–92) 64.4 (55.3–73.5) (45.1–92)
Gen. et sp. nov. CMNH 50233 67.4 (56.8–78) (47.2–96.3) 67.4 (57.9–77) (47.2–96.3)
Dickosteus threiplandi NHMUK PV P 49663 68.3 (57.6–79.1) (47.8–97.6) 68.3 (58.7–78) (47.8–97.6)
Amazichthys trinajsticae AA.MEM.DS.8 76.4 (64.4–88.4) (53.5–109.2) 76.4 (65.6–87.3) (53.5–109.2)
Dunkleosteus terrelli CMNH 7424 185.2 (156.2–214.3) (129.5–265) 185.2 (159–211.5) (129.5–265)
Dunkleosteus terrelli CMNH 6090 274.2 (231.1–317.2) (191.5–392.6) 274.2 (235.4–313) (191.5–392.6)
Dunkleosteus terrelli CMNH 7054 285.7 (240.8–330.5) (199.5–409) 285.7 (245.2–326.1) (199.5–409)
Dunkleosteus terrelli CMNH 5768 327.8 (276.3–379.3) (228.8–469.5) 327.8 (281.4–374.2) (228.8–469.5)

10.3 Predicting using sharks only

Note: this model calculated excluding Rajiformes, Chimaeriformes, and Alopias

fit.sharks<-lm(log(total_length)~log(OOL),
                data_final%>%
                 filter(clade=="Chondrichthyes",
                        !(order %in% c("Rajiformes","Chimaeriformes")),
                        genus!="Alopias",
                        length_as!="estimated t.l."))

summary(fit.sharks)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL), data = data_final %>% 
##     filter(clade == "Chondrichthyes", !(order %in% c("Rajiformes", 
##         "Chimaeriformes")), genus != "Alopias", length_as != 
##         "estimated t.l."))
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.39445 -0.09146 -0.00525  0.09698  0.55179 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 2.180860   0.021976   99.24   <2e-16 ***
## log(OOL)    0.885207   0.007596  116.54   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1457 on 538 degrees of freedom
## Multiple R-squared:  0.9619, Adjusted R-squared:  0.9618 
## F-statistic: 1.358e+04 on 1 and 538 DF,  p-value: < 2.2e-16
regression.stats(fit.sharks)
predict.shark<-(predict(fit.sharks,
           fossil_taxa%>%filter(!is.na(OOL)),
           interval="prediction")%>%
  exp()*regression.stats(fit.sharks)$CF)%>%
  data.frame()%>%rownames_to_column()%>%
  arrange(fit)%>%
  left_join(fossil_taxa%>%rownames_to_column(),by="rowname")%>%
  filter(!!fossil.specimens)%>%
  select(taxon,specimen,total_length,fit,lwr,upr)
predict.shark%>%
  kable(digits=1,
        caption= "Length estimates for specimens of <i>Dunkleosteus terrelli</i> and other arthrodires using a model considering only chondrichthyans, excluding rays, chimeras, and <i>Alopias</i>",
        col.names = c("Taxon","Specimen","Actual Length","Est.","Lwr 95% P.I.","Upr 95% P.I."))%>%
  column_spec(1, italic = T)%>%
  column_spec(4, bold = T)%>%
  kable_styling()
Table 10.6: Length estimates for specimens of Dunkleosteus terrelli and other arthrodires using a model considering only chondrichthyans, excluding rays, chimeras, and Alopias
Taxon Specimen Actual Length Est. Lwr 95% P.I. Upr 95% P.I.
Millerosteus minor FMNH PF 1089 13.7 16.8 12.6 22.5
Millerosteus minor Composite Millerosteus 14.9 19.2 14.4 25.6
Africanaspis dorissa Gess and Trinajstic 2017 23.0 27.9 20.9 37.1
Incisoscutum ritchei Recon. (Trinajstic 2013) 30.3 35.0 26.3 46.7
Coccosteus cuspidatus NMS 1893.107.27 29.6 38.4 28.8 51.2
Coccosteus cuspidatus NMS 1897.55.6 32.3 39.3 29.5 52.4
Coccosteus cuspidatus FMNH PF 1673 37.1 39.8 29.9 53.0
Coccosteus cuspidatus ROM VP 52664 37.5 45.6 34.2 60.7
Coccosteus cuspidatus NMS 1900.12.12 34.4 46.1 34.6 61.5
Coccosteus cuspidatus Recon. (M & W 1968) 39.4 46.9 35.2 62.5
Holonema westolli Recon. (Miles 1971) 60.6 53.7 40.3 71.6
Plourdosteus canadensis MNHM 2-177 37.5 53.9 40.5 71.8
Dickosteus threiplandi NMS 1987.7.118 43.7 58.3 43.8 77.7
Watsonosteus fletti NMS G.1995.4.2 56.6 66.7 50.1 88.9
Gen. et sp. nov. CMNH 50233 63.0 69.6 52.3 92.7
Dickosteus threiplandi NHMUK PV P 49663 52.3 70.5 52.9 93.9
Amazichthys trinajsticae AA.MEM.DS.8 89.7 78.1 58.7 104.1
Dunkleosteus terrelli CMNH 7424 NA 176.5 132.6 235.1
Dunkleosteus terrelli CMNH 6090 NA 253.3 190.1 337.4
Dunkleosteus terrelli CMNH 7054 NA 263.0 197.4 350.4
Dunkleosteus terrelli CMNH 5768 NA 298.5 224.1 397.8

10.3.1 Model excluding Lamniformes and Echinorhiniformes

Because many of the very largest fishes in this analysis are Lamniformes or Echinorhiniformes. Lamniformes tend to show a distinctly longer OOL relative to their total length (whether this is due to their anteriorposteriorly short trunk, an elongated gill region, or both is unclear at this time). On the other hand, Echinorhinus shows an unusually long head among fishes and appears to display a truncated abdomen (i.e., postpelvic region of the body). Varojean (1972) notes that E. cookei displays an unusual positive allometry of the head and abdomen relative to the trunk, and it is possible this is indicative of some unusual developmental pattern resulting in the strange body proportions of this taxon. Because of these factors, it is worth questioning whether the regression line might be biased in favor of Lamniformes and Echinorhinformes, and thus producing shorter body length estimates in Dunkleosteus.

fit.lamniformes<-lm(log(total_length)~log(OOL),
                data_final%>%
                 filter(!order %in% c("Lamniformes","Echinorhiniformes"),
                        length_as!="estimated t.l."))
fit.lamniformes2<-lm(log(total_length)~log(OOL),
                data_species%>%
                 filter(!order %in% c("Lamniformes","Echinorhiniformes")))
fit.lamniformes3<-lm(log(total_length)~log(OOL)+shape,
                data_final%>%
                 filter(!order %in% c("Lamniformes","Echinorhiniformes"),
                        length_as!="estimated t.l."))

predict.lamniformes<-(fossil_taxa%>%
                        drop_na(OOL)%>%
                        filter(!!fossil.specimens)%>%
  mutate(fit=predict(fit.lamniformes,.,interval="prediction")[,1],
         lwr=predict(fit.lamniformes,.,interval="prediction")[,2],
         upr=predict(fit.lamniformes,.,interval="prediction")[,3],
         fit2=predict(fit.lamniformes2,.,interval="prediction")[,1],
         lwr2=predict(fit.lamniformes2,.,interval="prediction")[,2],
         upr2=predict(fit.lamniformes2,.,interval="prediction")[,3],
         fit3=predict(fit.lamniformes3,.,interval="prediction")[,1],
         lwr3=predict(fit.lamniformes3,.,interval="prediction")[,2],
         upr3=predict(fit.lamniformes3,.,interval="prediction")[,3],
         across(fit:upr,~exp(.)*regression.stats(fit.lamniformes)$CF),
         across(fit2:upr2,~exp(.)*regression.stats(fit.lamniformes2)$CF),
         across(fit3:upr3,~exp(.)*regression.stats(fit.lamniformes3)$CF))%>%
  arrange(fit)%>%
  filter(genus %in% c("Coccosteus","Dunkleosteus"))%>%
  select(taxon,specimen,fit,lwr,upr,fit2,lwr2,upr2,fit3,lwr3,upr3))
predict.lamniformes%>%
  mutate(range=paste0("(",round(lwr,1),"-",round(upr,1),")"),
         range2=paste0("(",round(lwr2,1),"-",round(upr2,1),")"),
         range3=paste0("(",round(lwr3,1),"-",round(upr3,1),")"))%>%
  rownames_to_column()%>%
  select(taxon,specimen,fit,range,fit2,range2,fit3,range3)%>%
  kable(digits=1,
        caption= "Length estimates for specimens of <i>Dunkleosteus terrelli</i> using a model considering all fishes except Lamniformes. This table focuses on results in <i>Dunkleosteus</i> as Lamniformes are expected to bias only the very upper parts of the regression model",
        col.names = c("Taxon","Specimen","Est.","95% P.I.","Est.","95% P.I.","Est.","95% P.I."))%>%
  add_header_above(c(" "=2,
                     "Individual Data"=2,
                     "Species Averages"=2,
                     "Individual Data, With Shape"=2))%>%
  column_spec(1, italic = T)%>%
  column_spec(c(3,5,7),bold=T)%>%
  kable_styling()
Table 10.7: Length estimates for specimens of Dunkleosteus terrelli using a model considering all fishes except Lamniformes. This table focuses on results in Dunkleosteus as Lamniformes are expected to bias only the very upper parts of the regression model
Individual Data
Species Averages
Individual Data, With Shape
Taxon Specimen Est. 95% P.I. Est. 95% P.I. Est. 95% P.I.
Coccosteus cuspidatus NMS 1893.107.27 35.3 (22.8-54.7) 35.0 (22.1-55.3) 33.1 (22.9-47.7)
Coccosteus cuspidatus NMS 1897.55.6 36.3 (23.4-56.2) 35.9 (22.7-56.7) 34.0 (23.6-48.9)
Coccosteus cuspidatus FMNH PF 1673 36.8 (23.7-57) 36.4 (23-57.5) 34.4 (23.9-49.6)
Coccosteus cuspidatus ROM VP 52664 42.9 (27.7-66.5) 42.3 (26.8-66.8) 40.0 (27.8-57.7)
Coccosteus cuspidatus NMS 1900.12.12 43.5 (28.1-67.4) 42.9 (27.1-67.8) 40.6 (28.2-58.5)
Coccosteus cuspidatus Recon. (M & W 1968) 44.3 (28.6-68.7) 43.7 (27.6-69) 41.3 (28.7-59.6)
Dunkleosteus terrelli CMNH 7424 201.2 (129.8-311.7) 191.5 (121.1-303) 181.6 (126-261.7)
Dunkleosteus terrelli CMNH 6090 303.7 (196-470.6) 286.5 (181-453.4) 271.7 (188.5-391.7)
Dunkleosteus terrelli CMNH 7054 317.1 (204.6-491.4) 298.8 (188.8-472.9) 283.4 (196.6-408.6)
Dunkleosteus terrelli CMNH 5768 366.4 (236.4-567.8) 344.1 (217.4-544.7) 326.5 (226.4-470.7)

Omitting Lamniformes and Echinorhinoformes from the analysis results in a slight inflation of body length relative to other models. However this is mostly in the individual specimen model without any additional assumptions, and mostly due to the heavy sampling of long-bodied Istiophoriformes (e.g., Tetrapturus) in this data set. Using species averages or using a model that also considers shape produces results much closer to those without Lamniformes or Echinorhiniformes.

Additionally, the results of the model excluding Lamniformes are not considered very reliable as they result in the exclusion of taxa that would be expected to be most similar to Dunkleosteus in ecology and body shape (Carr 2010, Ferrón et al. 2017). Although lamnids and other pelagic lamniformes show a distinct underestimate of body length using OOL, Dunkleosteus might be expected to be more likely to show a more similar proportional relationship to lamnids than any other taxon (i.e., having a shorter trunk and longer OOL relative to total length than taxa like Coccosteus) given it has also been interpreted as an active pelagic swimmer (Carr 2010).

10.4 Using OOL to estimate total length minus snout length and then adding snout length on as an integer

Because arthrodires have a proportionally shorter snout than most other fishes, one question is whether this could have a biasing effect on the model because it assumes that arthrodires have rostral proportions similar to other fishes. Thus, a model was fitted seeing how well OOL predicted total length minus snout length, and then adding snout length back on as an integer because this was a known, measurable value in specimens of Dunkleosteus terrelli.

fit.minus_snout_length1<-lm(log(total_length-snout_length)~log(OOL)+(family=="Serranidae")+(family=="Holocentridae"),data_final)
fit.minus_snout_length2<-lm(log(total_length-snout_length)~log(OOL)+(family=="Serranidae")+(family=="Holocentridae"),data_species)

summary(fit.minus_snout_length1)
## 
## Call:
## lm(formula = log(total_length - snout_length) ~ log(OOL) + (family == 
##     "Serranidae") + (family == "Holocentridae"), data = data_final)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.65536 -0.12362 -0.00575  0.11336  1.59565 
## 
## Coefficients:
##                                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                    1.871337   0.008133  230.10   <2e-16 ***
## log(OOL)                       0.986866   0.003694  267.17   <2e-16 ***
## family == "Serranidae"TRUE    -0.378178   0.014519  -26.05   <2e-16 ***
## family == "Holocentridae"TRUE -0.330185   0.031654  -10.43   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1982 on 3165 degrees of freedom
##   (53 observations deleted due to missingness)
## Multiple R-squared:  0.9587, Adjusted R-squared:  0.9586 
## F-statistic: 2.446e+04 on 3 and 3165 DF,  p-value: < 2.2e-16
rbind("Individual Data"=regression.stats(fit.minus_snout_length1),
      "Species Averages"=regression.stats(fit.minus_snout_length2))

Note that the summary statistics presented here are for total length - snout length, not total length. Snout length must then be added back on as an integer to get the true total length.

fossil_taxa%>%
  filter(!!fossil.specimens,!is.na(snout_length))%>%
  augment(fit.minus_snout_length1,newdata=.,interval="prediction")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.minus_snout_length1)$CF+
                  snout_length),
         .PE=paste0("(",round(.fitted*(1-
                            regression.stats(fit.minus_snout_length1)$adjPE/100),1),
                    "–",round(.fitted*(1+
                            regression.stats(fit.minus_snout_length1)$adjPE/100),1),")"),
         .range=paste0("(",round(.lower,1),
                       "–",round(.upper,1),
                       ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit1",.)))%>%
  augment(fit.minus_snout_length2,newdata=.,interval="prediction")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.OOL)$CF+
                  snout_length),
         .PE=paste0("(",round(.fitted*(1-
                           regression.stats(fit.minus_snout_length2)$adjPE/100),1),
                    "–",round(.fitted*(1+
                           regression.stats(fit.minus_snout_length2)$adjPE/100),1),")"),
         .range=paste0("(",round(.lower,1),
                       "–",round(.upper,1),
                       ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit2",.)))%>%
  select(taxon,specimen,total_length,fit1.fitted,fit1.PE,fit1.range,fit2.fitted,fit2.PE,fit2.range)%>%
  kable(digits=1,align=c("l","c","c","c","c","c","c","c"),
        caption= "Length estimates for specimens of <i>Dunkleosteus terrelli</i> using a model estimating total length minus snout length using OOL and treating snout length as a known integer",
        col.names=c("Taxon","Specimen","Actual Length","Est.","+/- %PE","95% P.I.",
                    "Est.","+/- %PE","95% P.I."))%>%
  add_header_above(c(" "=3,"Individual Data"=3,"Species Averages"=3))%>%
  column_spec(1, italic = T)%>%
  column_spec(c(4,7), bold = T)%>%
  kable_styling()
Table 10.8: Length estimates for specimens of Dunkleosteus terrelli using a model estimating total length minus snout length using OOL and treating snout length as a known integer
Individual Data
Species Averages
Taxon Specimen Actual Length Est. +/- %PE 95% P.I. Est. +/- %PE 95% P.I.
Millerosteus minor FMNH PF 1089 13.7 13.6 (11.5–15.7) (9.3–19.9) 13.6 (11.4–15.7) (9.1–20.4)
Millerosteus minor Composite Millerosteus 14.9 16.0 (13.5–18.4) (11–23.3) 15.9 (13.4–18.4) (10.7–23.8)
Africanaspis dorissa Gess and Trinajstic 2017 23.0 24.3 (20.6–28) (16.8–35.4) 24.1 (20.3–28) (16.3–36)
Incisoscutum ritchei Recon. (Trinajstic 2013) 30.3 31.2 (26.4–36) (21.5–45.5) 30.9 (26–35.8) (20.8–46.2)
Coccosteus cuspidatus NMS 1893.107.27 29.6 34.4 (29.1–39.7) (23.7–50.3) 34.1 (28.7–39.6) (22.9–51.1)
Coccosteus cuspidatus NMS 1897.55.6 32.3 35.4 (29.9–40.9) (24.4–51.7) 35.1 (29.5–40.7) (23.6–52.5)
Coccosteus cuspidatus FMNH PF 1673 37.1 36.1 (30.5–41.6) (24.9–52.5) 35.7 (30–41.4) (24.1–53.4)
Coccosteus cuspidatus Recon. (M & W 1968) 39.4 42.3 (35.8–48.8) (28.8–62.1) 41.8 (35.1–48.5) (27.8–63)
Coccosteus cuspidatus NMS 1900.12.12 34.4 41.7 (35.2–48.1) (28.5–61.1) 41.2 (34.7–47.8) (27.5–62)
Coccosteus cuspidatus ROM VP 52664 37.5 41.6 (35.2–48) (28.6–60.8) 41.2 (34.6–47.8) (27.6–61.7)
Plourdosteus canadensis MNHM 2-177 37.5 49.8 (42.1–57.5) (34.1–72.9) 49.2 (41.4–57.1) (32.9–73.9)
Dickosteus threiplandi NMS 1987.7.118 43.7 53.8 (45.5–62.1) (36.7–79) 53.1 (44.7–61.6) (35.3–80.1)
Holonema westolli Recon. (Miles 1971) 60.6 50.6 (42.8–58.3) (34.9–73.6) 50.0 (42–58) (33.7–74.6)
Watsonosteus fletti NMS G.1995.4.2 56.6 63.4 (53.7–73.2) (43.6–92.7) 62.6 (52.6–72.6) (42–93.9)
Gen. et sp. nov. CMNH 50233 63.0 66.9 (56.6–77.2) (46.1–97.7) 66.0 (55.5–76.6) (44.4–98.8)
Dickosteus threiplandi NHMUK PV P 49663 52.3 68.0 (57.5–78.5) (46.9–99.2) 67.1 (56.4–77.8) (45.1–100.3)
Amazichthys trinajsticae AA.MEM.DS.8 89.7 75.6 (64–87.3) (51.9–110.6) 74.6 (62.7–86.5) (49.9–111.8)
Dunkleosteus terrelli CMNH 7424 NA 187.6 (158.7–216.5) (128.8–274.4) 183.7 (154.4–213) (123–275.6)
Dunkleosteus terrelli CMNH 6090 NA 280.5 (237.2–323.7) (192.5–410.2) 273.9 (230.2–317.6) (183.3–411.1)
Dunkleosteus terrelli CMNH 7054 NA 292.9 (247.8–338.1) (201.2–428.3) 286.0 (240.3–331.6) (191.5–429.1)
Dunkleosteus terrelli CMNH 5768 NA 336.8 (284.9–388.7) (231.1–492.7) 328.5 (276.1–380.9) (219.8–493.2)
data_final%>%
  mutate(fit1=exp(predict(fit.OOL,.))*
           regression.stats(fit.OOL)$CF,
         fit2=exp(predict(fit.minus_snout_length1,.))*
           regression.stats(fit.minus_snout_length1)$CF,
         fit3=exp(predict(fit.minus_snout_length2,.))*
           regression.stats(fit.minus_snout_length2)$CF,
         PE1=((fit1-total_length)/fit1)*100,
         PE2=(((fit2+snout_length)-total_length)/(fit2+snout_length))*100,
         PE3=(((fit3+snout_length)-total_length)/(fit3+snout_length))*100)%>%
  drop_na(PE1,PE2,PE3)%>%
  summarise(N=n(),PE1=mean(abs(PE1)),PE2=mean(abs(PE2)),PE3=mean(abs(PE3)))%>%
  kable(digits=c(1,2,2,2),align=c("l","c","c","c"),
        col.names=c("N","Using OOL to estimate total length","Individual data","Species averages"),
        caption="Comparison of PE<sub>cf</sub> between models treating snout length as a separate integer and those using OOL to estimate total length by itself. This statistic had to be calculated separately because the regression model for the former is between OOL and 'total length minus snout length', not total length.")%>%
  add_header_above(c(" "=2,"Treating snout length as a separate integer"=2))%>%
  kable_styling()
Table 10.9: Comparison of PEcf between models treating snout length as a separate integer and those using OOL to estimate total length by itself. This statistic had to be calculated separately because the regression model for the former is between OOL and ‘total length minus snout length’, not total length.
Treating snout length as a separate integer
N Using OOL to estimate total length Individual data Species averages
3169 17.55 14.2 14.32

10.5 Predicting using a model that includes shape as a covariate

fit.with_shape<-lm(log(total_length)~log(OOL)+shape+(family=="Serranidae")+(family=="Holocentridae"),
           data_initial%>%filter(!family %in% c("Balistidae","Monacanthidae"),
                                 !genus %in% c("Regalecus","Rhinochimaera"))%>%
             drop_na(shape))
summary(fit.with_shape)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) + shape + (family == 
##     "Serranidae") + (family == "Holocentridae"), data = data_initial %>% 
##     filter(!family %in% c("Balistidae", "Monacanthidae"), !genus %in% 
##         c("Regalecus", "Rhinochimaera")) %>% drop_na(shape))
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.64490 -0.09053 -0.00330  0.09049  0.80988 
## 
## Coefficients:
##                                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                    1.890335   0.006705 281.935  < 2e-16 ***
## log(OOL)                       0.980615   0.002907 337.341  < 2e-16 ***
## shapeanguilliform              0.209191   0.008395  24.918  < 2e-16 ***
## shapecompressiform            -0.114682   0.010760 -10.658  < 2e-16 ***
## shapeelongate                  0.224995   0.008267  27.218  < 2e-16 ***
## shapeflattened                 0.060436   0.030307   1.994   0.0462 *  
## shapemacruriform               0.368367   0.026763  13.764  < 2e-16 ***
## family == "Serranidae"TRUE    -0.333454   0.010803 -30.866  < 2e-16 ***
## family == "Holocentridae"TRUE -0.200061   0.025517  -7.840 5.97e-15 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1589 on 3389 degrees of freedom
##   (52 observations deleted due to missingness)
## Multiple R-squared:  0.9741, Adjusted R-squared:  0.9741 
## F-statistic: 1.596e+04 on 8 and 3389 DF,  p-value: < 2.2e-16
regression.stats(fit.with_shape)
fossil_taxa%>%
  filter(!!fossil.specimens)%>%
  augment(fit.with_shape,
          newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.with_shape)$CF))%>%
  mutate(range1=paste0("(",round(.lower,1),"–",round(.upper,1),")"),
         PE1_lower=.fitted*(1-(regression.stats(fit.with_shape)$adjPE)/100),
         PE1_lower=.fitted*(1-(regression.stats(fit.with_shape)$adjPE)/100),
         PE1_upper=.fitted*(1+(regression.stats(fit.with_shape)$adjPE)/100),
         PE_range1=paste0("(",round(PE1_lower,1),"–",round(PE1_upper,1),")"))%>%
  select(taxon,specimen,total_length,.fitted,PE_range1,range1)%>%
  kable(digits=1,
        align=c("l",rep("c",5)),
        caption= "Length estimates for specimens of <i>Dunkleosteus terrelli</i> and other arthrodires using a model that includes shape as an additional categorical covariate",
        col.names = c("Taxon","Specimen","Actual Length","Est.","+/- %PE","95% P.I."))%>%
  column_spec(1, italic = T)%>%
  column_spec(4, bold = T)%>%
  kable_styling()
Table 10.10: Length estimates for specimens of Dunkleosteus terrelli and other arthrodires using a model that includes shape as an additional categorical covariate
Taxon Specimen Actual Length Est. +/- %PE 95% P.I.
Millerosteus minor FMNH PF 1089 13.7 13.4 (11.8–15.1) (9.8–18.4)
Millerosteus minor Composite Millerosteus 14.9 15.5 (13.6–17.4) (11.4–21.2)
Africanaspis dorissa Gess and Trinajstic 2017 23.0 23.5 (20.7–26.3) (17.2–32.1)
Incisoscutum ritchei Recon. (Trinajstic 2013) 30.3 30.3 (26.6–33.9) (22.2–41.3)
Coccosteus cuspidatus NMS 1893.107.27 29.6 33.5 (29.5–37.6) (24.5–45.8)
Coccosteus cuspidatus NMS 1897.55.6 32.3 34.4 (30.3–38.6) (25.2–47)
Coccosteus cuspidatus FMNH PF 1673 37.1 34.9 (30.7–39) (25.5–47.6)
Coccosteus cuspidatus Recon. (M & W 1968) 39.4 41.8 (36.8–46.8) (30.6–57.1)
Coccosteus cuspidatus NMS 1900.12.12 34.4 41.1 (36.1–46) (30.1–56.1)
Coccosteus cuspidatus ROM VP 52664 37.5 40.5 (35.6–45.4) (29.6–55.3)
Plourdosteus canadensis MNHM 2-177 37.5 48.8 (42.9–54.7) (35.7–66.6)
Dickosteus threiplandi NMS 1987.7.118 43.7 53.2 (46.8–59.6) (39–72.7)
Holonema westolli Recon. (Miles 1971) 60.6 48.6 (42.8–54.4) (35.6–66.4)
Watsonosteus fletti NMS G.1995.4.2 56.6 61.8 (54.3–69.2) (45.2–84.4)
Gen. et sp. nov. CMNH 50233 63.0 64.7 (56.9–72.5) (47.4–88.4)
Dickosteus threiplandi NHMUK PV P 49663 52.3 65.6 (57.8–73.5) (48.1–89.7)
Amazichthys trinajsticae AA.MEM.DS.8 89.7 92.2 (81.1–103.2) (67.5–125.9)
Dunkleosteus terrelli CMNH 7424 NA 181.5 (159.7–203.3) (132.9–247.9)
Dunkleosteus terrelli CMNH 6090 NA 270.7 (238.2–303.3) (198.2–369.8)
Dunkleosteus terrelli CMNH 7054 NA 282.3 (248.3–316.3) (206.7–385.6)
Dunkleosteus terrelli CMNH 5768 NA 324.8 (285.8–363.9) (237.8–443.7)

10.6 Excluding acanthopterygian fishes

Because some of the highest systematic residuals in the model that could not be explained by changes in body shape were in acanthopterygian clades (i.e., Serranidae, Holocentridae, etc.) and acanthopterygians in general often show shortened or compressiform bodies with anteriorly positioned pelvic girdles (and the associated side effects on body shape), it is worth considering if the shorter lengths for Dunkleosteus terrelli predicted here might be biased by the inclusion of acanthopterygian lineages, which are expected to not be very similar to placoderms in body shape.

Because so many non-acanthopterygian actinopterygians have elongate bodies (gars, polypterids, eels, etc.) that are not typical for fishes more generally (especially when compared against the non-acanthopterygian fossil record), it is worth controlling for the effects of body shape on the model by excluding highly elongate taxa. Models were fit doing both below.

fit.no_acanthopterygii<-lm(log(total_length)~log(OOL),
                               data_final%>%filter(higher_group!="Acanthoptergyii"))
fit.no_acanthopterygii2<-lm(log(total_length)~log(OOL),
                               data_final%>%filter(higher_group!="Acanthoptergyii",
                                                   shape %in% c("elongate",
                                                            "fusiform")))
fit.no_acanthopterygii3<-lm(log(total_length)~log(OOL),
                               data_final%>%filter(higher_group!="Acanthoptergyii",
                                                   shape=="fusiform"))
summary(fit.no_acanthopterygii)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL), data = data_final %>% 
##     filter(higher_group != "Acanthoptergyii"))
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.61436 -0.14237  0.00201  0.13581  1.55312 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 1.900761   0.009076   209.4   <2e-16 ***
## log(OOL)    0.996190   0.004175   238.6   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2248 on 3167 degrees of freedom
##   (53 observations deleted due to missingness)
## Multiple R-squared:  0.9473, Adjusted R-squared:  0.9473 
## F-statistic: 5.693e+04 on 1 and 3167 DF,  p-value: < 2.2e-16
rbind(
  "All Taxa"=regression.stats(fit.no_acanthopterygii),
  "Excluding anguilliform and macruriform taxa"=regression.stats(fit.no_acanthopterygii2),
  "Fusiform taxa only"=regression.stats(fit.no_acanthopterygii3))
fossil_taxa%>%
  filter(length_as=="total length"|specimen %in% c("CMNH 5768","CMNH 7424","CMNH 6090","CMNH 7054"))%>%
  augment(fit.no_acanthopterygii,newdata=.,interval="prediction")%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit1",.)))%>%
  augment(fit.no_acanthopterygii2,newdata=.,interval="prediction")%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit2",.)))%>%
  augment(fit.no_acanthopterygii3,newdata=.,interval="prediction")%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit3",.)))%>%
  mutate(across(fit1.fitted:fit1.upper,~exp(.)*regression.stats(fit.no_acanthopterygii)$CF),
         across(fit2.fitted:fit2.upper,~exp(.)*regression.stats(fit.no_acanthopterygii2)$CF),
         across(fit3.fitted:fit3.upper,~exp(.)*regression.stats(fit.no_acanthopterygii3)$CF))%>%
  select(taxon,specimen,total_length,fit1.fitted,fit1.lower,fit1.upper,
         fit2.fitted,fit2.lower,fit2.upper,
         fit3.fitted,fit3.lower,fit3.upper)%>%
  arrange(fit1.fitted)%>%
  kable(digits=1,align=c("l","c","c","c","c","c","c","c","c","c","c","c"),
        caption= "Length estimates for specimens of <i>Dunkleosteus terrelli</i> using a model that excludes acanthopterygian fishes",
        col.names = c("Taxon","Specimen","Actual Length","Est.","Lwr 95% P.I.","Upr 95% P.I.",
                      "Est.","Lwr 95% P.I.","Upr 95% P.I.",
                      "Est.","Lwr 95% P.I.","Upr 95% P.I."))%>%
  column_spec(1, italic = T)%>%
  column_spec(c(3,4,7,10), bold = T)%>%
  add_header_above(c(" "=3,"All non-acanthopterygian fishes"=3,"Excluding anguilliform and macruriform taxa"=3,"Fusiform non-acanthopterygians only"=3))%>%
  kable_styling()
Table 10.11: Length estimates for specimens of Dunkleosteus terrelli using a model that excludes acanthopterygian fishes
All non-acanthopterygian fishes
Excluding anguilliform and macruriform taxa
Fusiform non-acanthopterygians only
Taxon Specimen Actual Length Est. Lwr 95% P.I. Upr 95% P.I. Est. Lwr 95% P.I. Upr 95% P.I. Est. Lwr 95% P.I. Upr 95% P.I.
Millerosteus minor FMNH PF 1089 13.7 13.9 8.9 21.6 13.7 9.1 20.6 13.3 9.3 19.0
Millerosteus minor Composite Millerosteus 14.9 16.0 10.3 24.9 15.8 10.5 23.8 15.3 10.7 21.9
Africanaspis dorissa Gess and Trinajstic 2017 23.0 24.4 15.7 38.0 24.0 15.9 36.2 23.1 16.2 33.0
Incisoscutum ritchei Recon. (Trinajstic 2013) 30.3 31.6 20.3 49.1 31.0 20.5 46.7 29.7 20.8 42.4
Coccosteus cuspidatus NMS 1893.107.27 29.6 35.1 22.6 54.5 34.4 22.8 51.8 32.9 23.0 47.0
Coccosteus cuspidatus NMS 1897.55.6 32.3 36.0 23.2 56.0 35.3 23.4 53.2 33.7 23.6 48.2
Coccosteus cuspidatus FMNH PF 1673 37.1 36.5 23.5 56.7 35.7 23.7 53.9 34.2 23.9 48.8
Coccosteus cuspidatus ROM VP 52664 37.5 42.5 27.4 66.1 41.6 27.6 62.7 39.7 27.8 56.6
Coccosteus cuspidatus NMS 1900.12.12 34.4 43.1 27.8 67.0 42.2 28.0 63.6 40.2 28.1 57.4
Coccosteus cuspidatus Recon. (M & W 1968) 39.4 43.9 28.3 68.3 43.0 28.5 64.8 40.9 28.7 58.5
Holonema westolli Recon. (Miles 1971) 60.6 51.2 32.9 79.5 50.0 33.2 75.4 47.5 33.3 67.9
Plourdosteus canadensis MNHM 2-177 37.5 51.4 33.1 79.9 50.2 33.3 75.7 47.7 33.4 68.2
Dickosteus threiplandi NMS 1987.7.118 43.7 56.1 36.1 87.2 54.8 36.3 82.6 52.0 36.4 74.3
Watsonosteus fletti NMS G.1995.4.2 56.6 65.3 42.0 101.5 63.7 42.2 96.1 60.3 42.2 86.1
Gen. et sp. nov. CMNH 50233 63.0 68.5 44.1 106.4 66.8 44.3 100.7 63.2 44.2 90.2
Dickosteus threiplandi NHMUK PV P 49663 52.3 69.5 44.7 108.0 67.7 44.9 102.2 64.1 44.8 91.5
Amazichthys trinajsticae AA.MEM.DS.8 89.7 78.0 50.2 121.2 76.0 50.4 114.7 71.7 50.2 102.5
Dunkleosteus terrelli CMNH 7424 NA 195.2 125.6 303.4 189.2 125.4 285.3 175.7 123.0 251.0
Dunkleosteus terrelli CMNH 6090 NA 293.0 188.5 455.5 283.3 187.8 427.3 261.3 182.9 373.3
Dunkleosteus terrelli CMNH 7054 NA 305.7 196.7 475.2 295.5 195.9 445.8 272.4 190.6 389.2
Dunkleosteus terrelli CMNH 5768 NA 352.6 226.8 548.1 340.5 225.7 513.7 313.0 219.1 447.3

However, even when excluding acanthopterygian fishes, estimated lengths for Dunkleosteus were comparable to those of the acanthopterygian-only model. Thus, the short lengths for Dunkleosteus predicted here do not appear to be driven by very short-bodied or large-headed acanthopterygian taxa.

10.7 Considering both clade membership and body shape

One issue with the present model is most of the very largest fishes (3+ m) sampled tend to be pelagic Lamniformes or Echinorhinus spp. that are known to have very long heads relative to body length, whereas some of the most heavily sampled fishes larger than 1 m (i.e., between 1 and 2.5 m) are actinopterygian fishes with elongate trunk proportions (e.g., Tetrapturus, Kajikia, Coryphaena). This could potentially cause biases in estimates of body length for Dunkleosteus, especially given that Lamniformes and Echinorhiniformes tend to bias the correlation between total_length and OOL for sharks.

Because of this, a model was fit containing both clade and shape, assuming that shape influences the model as an independent variable (given the residuals due to shape are primarily seen in the intercept) whereas different levels of clade were allowed to have different slopes (given Lamniformes and Echinorhiniformes drive a different pattern in sharks). This would be expected to estimate length in Dunkleosteus terrelli without it being biased either by fishes with elongate body shapes or by short-bodied lamnids.

Note: in this model membership in Serranidae or Holocentridae was treated as a factor as well, given that groupers and squirrelfishes have unusual proportions compared to other fishes

fit.shape_clade<-lm(log(total_length)~log(OOL)*clade+shape+(family=="Serranidae")+(family=="Holocentridae"),
                    data_final)
fit.shape_clade_species_averages<-lm(log(total_length)~log(OOL)*clade+shape+(family=="Serranidae")+(family=="Holocentridae"),
                    data_species)
fit.shape_clade3<-lm(log(total_length)~log(OOL)*(clade=="Chondrichthyes")+
                       shape+(family=="Serranidae")+(family=="Holocentridae"),
                    data_final)

10.7.1 Model including clade and body shape, using individual specimens

summary(fit.shape_clade)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) * clade + shape + (family == 
##     "Serranidae") + (family == "Holocentridae"), data = data_final)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.64651 -0.08889  0.00184  0.08189  1.17286 
## 
## Coefficients:
##                                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                       1.881731   0.007636 246.444  < 2e-16 ***
## log(OOL)                          0.994611   0.003768 263.933  < 2e-16 ***
## cladeChondrichthyes               0.146538   0.026060   5.623 2.04e-08 ***
## cladePetromyzontiformes          -0.466907   0.067481  -6.919 5.48e-12 ***
## cladePlacodermi                   0.062857   0.153789   0.409   0.6828    
## cladeSarcopterygii                1.261956   0.797538   1.582   0.1137    
## shapeanguilliform                 0.428570   0.018306  23.411  < 2e-16 ***
## shapecompressiform               -0.103080   0.011315  -9.110  < 2e-16 ***
## shapeelongate                     0.202419   0.009037  22.400  < 2e-16 ***
## shapeflattened                    0.058220   0.032441   1.795   0.0728 .  
## shapemacruriform                  0.404113   0.026750  15.107  < 2e-16 ***
## family == "Serranidae"TRUE       -0.325220   0.012314 -26.410  < 2e-16 ***
## family == "Holocentridae"TRUE    -0.205108   0.027221  -7.535 6.35e-14 ***
## log(OOL):cladeChondrichthyes     -0.069379   0.009473  -7.324 3.04e-13 ***
## log(OOL):cladePetromyzontiformes  0.098758   0.033894   2.914   0.0036 ** 
## log(OOL):cladePlacodermi         -0.073990   0.082868  -0.893   0.3720    
## log(OOL):cladeSarcopterygii      -0.427682   0.263398  -1.624   0.1045    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1625 on 3152 degrees of freedom
##   (53 observations deleted due to missingness)
## Multiple R-squared:  0.9726, Adjusted R-squared:  0.9725 
## F-statistic:  6993 on 16 and 3152 DF,  p-value: < 2.2e-16

10.7.2 Model including clade and body shape, using species averages

summary(fit.shape_clade_species_averages)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) * clade + shape + (family == 
##     "Serranidae") + (family == "Holocentridae"), data = data_species)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.61300 -0.09770 -0.00157  0.08416  1.14890 
## 
## Coefficients:
##                                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                       1.913225   0.015082 126.855  < 2e-16 ***
## log(OOL)                          0.972992   0.008394 115.912  < 2e-16 ***
## cladeChondrichthyes               0.119128   0.058268   2.044 0.041181 *  
## cladePetromyzontiformes          -0.601871   0.203911  -2.952 0.003239 ** 
## cladePlacodermi                   0.005422   0.226609   0.024 0.980915    
## cladeSarcopterygii                2.174474   2.077767   1.047 0.295576    
## shapeanguilliform                 0.453944   0.034448  13.178  < 2e-16 ***
## shapecompressiform               -0.069246   0.020145  -3.437 0.000613 ***
## shapeelongate                     0.170646   0.019247   8.866  < 2e-16 ***
## shapeflattened                    0.073032   0.051143   1.428 0.153620    
## shapemacruriform                  0.381784   0.046415   8.225 6.36e-16 ***
## family == "Serranidae"TRUE       -0.285899   0.024694 -11.578  < 2e-16 ***
## family == "Holocentridae"TRUE    -0.243648   0.053653  -4.541 6.31e-06 ***
## log(OOL):cladeChondrichthyes     -0.044876   0.021958  -2.044 0.041261 *  
## log(OOL):cladePetromyzontiformes  0.146836   0.143658   1.022 0.306981    
## log(OOL):cladePlacodermi         -0.026538   0.117171  -0.226 0.820873    
## log(OOL):cladeSarcopterygii      -0.717417   0.709414  -1.011 0.312139    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1848 on 950 degrees of freedom
##   (4 observations deleted due to missingness)
## Multiple R-squared:  0.9598, Adjusted R-squared:  0.9592 
## F-statistic:  1419 on 16 and 950 DF,  p-value: < 2.2e-16

10.7.3 Model including clade and body shape, only considering membership in Chondrichthyes as important phylogenetic information

However, one issue with this is the small number of arthrodire taxa for which total length data was available. This means that the coefficient for placoderms has a great degree of uncertainty (=standard error). Examining the results of the model containing different coefficients for major fish clades found that only Chondrichthyes significantly differed from the other fish groups in slope, largely because the largest chondrichthyans are all short-bodied (mostly lamnids or other lamniforms like Megachasma). This suggests it might be better to simply consider a model treating membership in Chondrichthyes as a binary variable, as this would greatly lower uncertainty in the coefficient for clade and thus improve model accuracy when estimating length in Dunkleosteus and other arthrodires.

summary(fit.shape_clade3)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) * (clade == "Chondrichthyes") + 
##     shape + (family == "Serranidae") + (family == "Holocentridae"), 
##     data = data_final)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.64269 -0.09281 -0.00968  0.08598  1.37524 
## 
## Coefficients:
##                                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                             1.881925   0.007824 240.524  < 2e-16 ***
## log(OOL)                                0.993271   0.003830 259.341  < 2e-16 ***
## clade == "Chondrichthyes"TRUE           0.147315   0.026872   5.482 4.54e-08 ***
## shapeanguilliform                       0.204540   0.009156  22.339  < 2e-16 ***
## shapecompressiform                     -0.101398   0.011667  -8.691  < 2e-16 ***
## shapeelongate                           0.203976   0.009294  21.946  < 2e-16 ***
## shapeflattened                          0.056591   0.033489   1.690   0.0912 .  
## shapemacruriform                        0.402741   0.027612  14.585  < 2e-16 ***
## family == "Serranidae"TRUE             -0.322939   0.012690 -25.449  < 2e-16 ***
## family == "Holocentridae"TRUE          -0.205049   0.028101  -7.297 3.71e-13 ***
## log(OOL):clade == "Chondrichthyes"TRUE -0.067440   0.009744  -6.921 5.41e-12 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1677 on 3158 degrees of freedom
##   (53 observations deleted due to missingness)
## Multiple R-squared:  0.9707, Adjusted R-squared:  0.9707 
## F-statistic: 1.048e+04 on 10 and 3158 DF,  p-value: < 2.2e-16

10.7.4 Regression statistics

rbind("Individual specimens"=regression.stats(fit.shape_clade),
      "Species averages"=regression.stats(fit.shape_clade_species_averages),
      "Only considering membership in Chondrichthyes"=regression.stats(fit.shape_clade3))

10.7.5 Predicting length in Dunkleosteus terrelli and other arthrodires

fossil_taxa%>%
  filter(!!fossil.specimens)%>%
  mutate(IsChondrichthyes=ifelse(clade=="Chondrichthyes",T,F))%>%
  augment(fit.shape_clade,newdata=.,interval="predict")%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit1",.)))%>%
  augment(fit.shape_clade_species_averages,newdata=.,interval="prediction")%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit2",.)))%>%
  augment(fit.shape_clade3,newdata=.,interval="prediction")%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit3",.)))%>%
  mutate(across(fit1.fitted:fit1.upper,~exp(.)*regression.stats(fit.shape_clade)$CF),
         across(fit2.fitted:fit2.upper,~exp(.)*regression.stats(fit.shape_clade_species_averages)$CF),
         across(fit3.fitted:fit3.upper,~exp(.)*regression.stats(fit.shape_clade3)$CF))%>%
  select(taxon,specimen,total_length,fit1.fitted,fit1.lower,fit1.upper,
         fit2.fitted,fit2.lower,fit2.upper,
         fit3.fitted,fit3.lower,fit3.upper)%>%
  arrange(fit1.fitted)%>%
  kable(digits=1,align=c("l","c","c","c","c","c","c","c","c","c","c","c"),
        caption= "Length estimates for specimens of <i>Dunkleosteus terrelli</i> using a model considering both clade and body shape. 'Chondrichthyan-specific model' refers to model where only group membership considered was whether or not taxon was a member of Chondrichthyes.",
        col.names = c("Taxon","Specimen","Actual Length","Est.","Lwr 95% P.I.","Upr 95% P.I.",
                      "Est.","Lwr 95% P.I.","Upr 95% P.I.",
                      "Est.","Lwr 95% P.I.","Upr 95% P.I."))%>%
  column_spec(1, italic = T)%>%
  column_spec(c(3,4,7,10), bold = T)%>%
  add_header_above(c(" "=3,"Individual Specimens"=3,"Species Averages"=3,"Chondrichthyan-Specific Model"=3))%>%
  kable_styling()
Table 10.12: Length estimates for specimens of Dunkleosteus terrelli using a model considering both clade and body shape. ‘Chondrichthyan-specific model’ refers to model where only group membership considered was whether or not taxon was a member of Chondrichthyes.
Individual Specimens
Species Averages
Chondrichthyan-Specific Model
Taxon Specimen Actual Length Est. Lwr 95% P.I. Upr 95% P.I. Est. Lwr 95% P.I. Upr 95% P.I. Est. Lwr 95% P.I. Upr 95% P.I.
Millerosteus minor FMNH PF 1089 13.7 13.7 9.4 19.8 13.6 8.6 21.6 13.5 9.7 18.8
Millerosteus minor Composite Millerosteus 14.9 15.6 10.9 22.4 15.6 10.0 24.4 15.6 11.3 21.7
Africanaspis dorissa Gess and Trinajstic 2017 23.0 23.1 16.5 32.4 23.3 15.6 34.9 23.8 17.1 33.1
Incisoscutum ritchei Recon. (Trinajstic 2013) 30.3 29.3 21.0 40.7 29.8 20.2 43.9 30.8 22.1 42.8
Coccosteus cuspidatus NMS 1893.107.27 29.6 32.2 23.2 44.8 32.9 22.4 48.2 34.1 24.6 47.4
Coccosteus cuspidatus NMS 1897.55.6 32.3 33.0 23.8 45.9 33.7 23.0 49.4 35.1 25.2 48.7
Coccosteus cuspidatus FMNH PF 1673 37.1 33.4 24.1 46.4 34.1 23.3 50.0 35.5 25.6 49.4
Coccosteus cuspidatus ROM VP 52664 37.5 38.5 27.7 53.4 39.4 27.0 57.7 41.3 29.7 57.4
Coccosteus cuspidatus NMS 1900.12.12 34.4 39.0 28.1 54.1 40.0 27.3 58.5 41.9 30.2 58.3
Coccosteus cuspidatus Recon. (M & W 1968) 39.4 39.7 28.6 55.1 40.7 27.8 59.5 42.7 30.7 59.4
Holonema westolli Recon. (Miles 1971) 60.6 45.7 32.8 63.5 47.0 32.1 68.9 49.7 35.8 69.1
Plourdosteus canadensis MNHM 2-177 37.5 45.8 33.0 63.8 47.2 32.2 69.2 49.9 35.9 69.4
Dickosteus threiplandi NMS 1987.7.118 43.7 49.7 35.7 69.3 51.3 34.9 75.4 54.5 39.2 75.8
Watsonosteus fletti NMS G.1995.4.2 56.6 57.2 40.8 80.1 59.3 40.1 87.7 63.4 45.6 88.1
Gen. et sp. nov. CMNH 50233 63.0 59.8 42.6 83.9 62.0 41.8 92.0 66.5 47.9 92.4
Dickosteus threiplandi NHMUK PV P 49663 52.3 60.6 43.1 85.0 62.9 42.4 93.3 67.4 48.5 93.7
Amazichthys trinajsticae AA.MEM.DS.8 89.7 82.5 58.5 116.5 83.2 55.6 124.7 92.9 66.8 129.1
Dunkleosteus terrelli CMNH 7424 NA 157.4 103.8 238.5 167.8 100.4 280.2 188.9 135.9 262.6
Dunkleosteus terrelli CMNH 6090 NA 229.0 144.7 362.6 246.8 138.2 440.6 283.3 203.8 393.8
Dunkleosteus terrelli CMNH 7054 NA 238.2 149.7 378.9 256.9 142.8 462.2 295.5 212.6 410.8
Dunkleosteus terrelli CMNH 5768 NA 271.8 168.0 439.6 294.2 159.4 542.8 340.7 245.1 473.6

As an example of what is meant by the small sample size of Arthrodira reducing model precision, compare the prediction intervals for the model including clade as a categorical variable with the one only using a binary variable considering whether or not a taxon belongs to Chondrichthyes. The prediction intervals of the latter are much smaller, and this is because of the much greater standard error for cladePlacodermi and the interaction between Placodermi and log(OOL) compared to Actinopterygii and Chondrichthyes when each clade is considered by themselves.

11 Allowing relative body depth to influence length estimates

ggplot(data_final%>%
         filter(!is.na(OOL),!is.na(total_length),length_as!="estimated t.l.")%>%
         mutate(residuals=residuals(fit.OOL),
                shape=relevel(
                  factor(ifelse(family=="Serranidae","Serranidae",as.character(shape))),ref="fusiform"))%>%
         filter(!family %in% c("Monacanthidae","Balistidae","Acanthuridae")),
       aes(I(body_depth/total_length),residuals))+
  geom_star(aes(fill=shape,starshape=clade))+
  geom_smooth(data=.%>%filter(!family %in% c("Serranidae", "Balistidae", "Holocentridae", "Acanthuridae", "Monacanthidae"),clade!="Petromyzontiformes"),aes(color="2"),formula=y~x,method="loess")+
  geom_smooth(formula=y~x,method="lm",aes(color="1"))+
  scale_starshape_manual(values=c(15,11,13,1,28))+
  scale_color_manual(labels=c("Lm model of all taxa","Loess fit excluding unusual taxa"),values=c(hue_pal()(2)))+
  labs(x="Aspect Ratio of Body (body depth/total length)",y="Residuals of OOL Equation",
       starshape="Clade",fill="Shape",colour="Color")+
  theme(legend.box = "horizontal",legend.position = c(0.55,0.7))+
  guides(fill = guide_legend(override.aes = list(starshape = 15)))
Plot between residuals of the all-specimen model (fit.OOL) and aspect ratio (body_depth/total_length). Taxa with posteriorly shifted orbits (Balistoidea and Acanthuridae) omitted to better show patterns in the model. For the loess model, “unusual taxa” refers to members of the clades Petromyzontiformes, Serranidae, Holocenridae, Acanthuridae, Balistidae, and Monacanthidae, which appear to show unusual patterns in residuals separate from the general non-linear correlation between the residuals and body shape.

Figure 11.1: Plot between residuals of the all-specimen model (fit.OOL) and aspect ratio (body_depth/total_length). Taxa with posteriorly shifted orbits (Balistoidea and Acanthuridae) omitted to better show patterns in the model. For the loess model, “unusual taxa” refers to members of the clades Petromyzontiformes, Serranidae, Holocenridae, Acanthuridae, Balistidae, and Monacanthidae, which appear to show unusual patterns in residuals separate from the general non-linear correlation between the residuals and body shape.

Note the non-linear relationship between body shape and the residuals of the model, in which there is next to no correlation between the residuals and body shape among compressiform taxa, but there is a pattern of ever-increasing residuals among elongate and anguilliform taxa (especially anguilliform gnathostomes), representing an acceleration in axial elongation relative to the head with increasing aspect ratio.

summary(lm(residuals~I(body_depth/total_length),
           data_final%>%
         filter(!is.na(OOL),!is.na(total_length),length_as!="estimated t.l.")%>%
         mutate(residuals=residuals(fit.OOL))))
## 
## Call:
## lm(formula = residuals ~ I(body_depth/total_length), data = data_final %>% 
##     filter(!is.na(OOL), !is.na(total_length), length_as != "estimated t.l.") %>% 
##     mutate(residuals = residuals(fit.OOL)))
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.51018 -0.11320 -0.01116  0.11167  1.41891 
## 
## Coefficients:
##                             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                 0.206770   0.007739   26.72   <2e-16 ***
## I(body_depth/total_length) -1.046554   0.034533  -30.31   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1943 on 2843 degrees of freedom
##   (324 observations deleted due to missingness)
## Multiple R-squared:  0.2442, Adjusted R-squared:  0.2439 
## F-statistic: 918.5 on 1 and 2843 DF,  p-value: < 2.2e-16

As noted by the above report, there is a significant correlation between the residuals of the model and aspect ratio.

11.1 Fitting models including body height as an explanatory variable

11.1.1 Model including body_depth as additional variable, both as a separate coefficient and considering interaction effects with OOL

fit.relativedepth1<-lm(log(total_length)~
                        log(OOL)*I(body_depth),
                      data_final %>% filter(length_as!="estimated t.l."))
summary(fit.relativedepth1)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) * I(body_depth), data = data_final %>% 
##     filter(length_as != "estimated t.l."))
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -0.5986 -0.1372  0.0104  0.1265  1.5669 
## 
## Coefficients:
##                          Estimate Std. Error t value Pr(>|t|)    
## (Intercept)             1.8648663  0.0114444 162.950   <2e-16 ***
## log(OOL)                1.0289687  0.0069719 147.587   <2e-16 ***
## I(body_depth)          -0.0033954  0.0016228  -2.092   0.0365 *  
## log(OOL):I(body_depth)  0.0004491  0.0003985   1.127   0.2599    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2227 on 2841 degrees of freedom
##   (338 observations deleted due to missingness)
## Multiple R-squared:  0.9446, Adjusted R-squared:  0.9445 
## F-statistic: 1.614e+04 on 3 and 2841 DF,  p-value: < 2.2e-16

11.1.2 Model including interaction between body_depth and OOL only

fit.relativedepth2<-lm(log(total_length)~
                        log(OOL)*log(body_depth)-log(body_depth),
                      data_final %>% filter(length_as!="estimated t.l."))
summary(fit.relativedepth2)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) * log(body_depth) - 
##     log(body_depth), data = data_final %>% filter(length_as != 
##     "estimated t.l."))
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.60144 -0.13620  0.00993  0.11746  1.56573 
## 
## Coefficients:
##                           Estimate Std. Error t value Pr(>|t|)    
## (Intercept)               1.839404   0.011329  162.36  < 2e-16 ***
## log(OOL)                  1.061878   0.009884  107.43  < 2e-16 ***
## log(OOL):log(body_depth) -0.013968   0.002340   -5.97 2.67e-09 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2218 on 2842 degrees of freedom
##   (338 observations deleted due to missingness)
## Multiple R-squared:  0.945,  Adjusted R-squared:  0.945 
## F-statistic: 2.442e+04 on 2 and 2842 DF,  p-value: < 2.2e-16

11.1.3 Model including coefficient only

fit.relativedepth3<-lm(log(total_length)~
                        log(OOL)+log(body_depth),
                      data_final %>% filter(length_as!="estimated t.l."))
summary(fit.relativedepth3)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) + log(body_depth), 
##     data = data_final %>% filter(length_as != "estimated t.l."))
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.60060 -0.13218  0.00929  0.10956  1.59636 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      1.893312   0.009690 195.381   <2e-16 ***
## log(OOL)         1.073542   0.008044 133.464   <2e-16 ***
## log(body_depth) -0.069984   0.007274  -9.622   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2197 on 2842 degrees of freedom
##   (338 observations deleted due to missingness)
## Multiple R-squared:  0.9461, Adjusted R-squared:  0.946 
## F-statistic: 2.493e+04 on 2 and 2842 DF,  p-value: < 2.2e-16

11.1.4 Model using integer of head length/body_depth to model effects of shape on regression

fit.relativedepth4<-lm(log(total_length)~
                        log(OOL)+I(head_length/body_depth),
                      data_final %>% filter(length_as!="estimated t.l."))
summary(fit.relativedepth4)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) + I(head_length/body_depth), 
##     data = data_final %>% filter(length_as != "estimated t.l."))
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -0.5752 -0.1218 -0.0119  0.1107  1.6061 
## 
## Coefficients:
##                           Estimate Std. Error t value Pr(>|t|)    
## (Intercept)               1.796274   0.010154  176.91   <2e-16 ***
## log(OOL)                  1.001132   0.004382  228.46   <2e-16 ***
## I(head_length/body_depth) 0.057405   0.003191   17.99   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2115 on 2842 degrees of freedom
##   (338 observations deleted due to missingness)
## Multiple R-squared:   0.95,  Adjusted R-squared:   0.95 
## F-statistic: 2.701e+04 on 2 and 2842 DF,  p-value: < 2.2e-16

11.2 Support statistics of models

rbind("With no Shape Information"=regression.stats(fit.OOL),
      "With Body Shape as Categorical Variable"=regression.stats(fit.with_shape),
      "With Coefficient and Interaction Term from Body Height"=regression.stats(fit.relativedepth1),
      "With Interaction Term Only"=regression.stats(fit.relativedepth2),
      "With Coefficient Only"=regression.stats(fit.relativedepth3),
      "Using Integer of Head Length/Body Height"=regression.stats(fit.relativedepth4))

However, when comparing the model support statistics, while it can be seen that including relative body height as a covariate produces slightly better support in terms of AIC, BIC, and %PE values than a model that does not consider body shape, they perform much more poorly than models that simply brute-forces variation in body shape into a single categorical variable. Thus, it is clear that although covariation between body shape and aspect ratio are affecting length estimates, simply adding in body height as a covarying term does not produce a significant improvement in results in the model here.

11.3 Estimated lengths for Dunkleosteus

fossil_taxa%>%
  filter(!!fossil.specimens,!is.na(body_depth),clade=="Placodermi")%>%
  augment(fit.relativedepth1,newdata=.,interval="prediction")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.relativedepth1)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit1",.)))%>%
  augment(fit.relativedepth2,newdata=.,interval="prediction")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.relativedepth2)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit2",.)))%>%
  augment(fit.relativedepth3,newdata=.,interval="prediction")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.relativedepth3)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit3",.)))%>%
  augment(fit.relativedepth4,newdata=.,interval="prediction")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.relativedepth4)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit4",.)))%>%
  arrange(fit1.fitted)%>%
  mutate(range1=paste0("(",round(fit1.lower,1),"-",round(fit1.upper,1),")"),
         range2=paste0("(",round(fit2.lower,1),"-",round(fit2.upper,1),")"),
         range3=paste0("(",round(fit3.lower,1),"-",round(fit3.upper,1),")"),
         range4=paste0("(",round(fit4.lower,1),"-",round(fit4.upper,1),")"),
         PE1_lower=fit1.fitted*(1-(regression.stats(fit.relativedepth1)$adjPE)/100),
         PE1_upper=fit1.fitted*(1+(regression.stats(fit.relativedepth1)$adjPE)/100),
         PE2_lower=fit2.fitted*(1-(regression.stats(fit.relativedepth2)$adjPE)/100),
         PE2_upper=fit2.fitted*(1+(regression.stats(fit.relativedepth2)$adjPE)/100),
         PE3_lower=fit3.fitted*(1-(regression.stats(fit.relativedepth3)$adjPE)/100),
         PE3_upper=fit3.fitted*(1+(regression.stats(fit.relativedepth3)$adjPE)/100),
         PE4_lower=fit4.fitted*(1-(regression.stats(fit.relativedepth4)$adjPE)/100),
         PE4_upper=fit4.fitted*(1+(regression.stats(fit.relativedepth4)$adjPE)/100),
         PE_range1=paste0("(",round(PE1_lower,1),"-",round(PE1_upper,1),")"),
         PE_range2=paste0("(",round(PE2_lower,1),"-",round(PE2_upper,1),")"),
         PE_range3=paste0("(",round(PE3_lower,1),"-",round(PE3_upper,1),")"),
         PE_range4=paste0("(",round(PE4_lower,1),"-",round(PE4_upper,1),")"))%>%
  select(taxon,specimen,total_length,
         fit1.fitted,PE_range1,range1,
         fit2.fitted,PE_range2,range2,
         fit3.fitted,PE_range3,range3,
         fit4.fitted,PE_range4,range4)%>%
  kable(digits=1,
        caption= "Length estimates for specimens of <i>Dunkleosteus terrelli</i> using a model that incorporates relative body depth. All lengths in cm.",
        col.names = c("Taxon","Specimen","Actual Length",
                      "Est.","+/- %PE","95% P.I.",
                      "Est.","+/- %PE","95% P.I.",
                      "Est.","+/- %PE","95% P.I.",
                      "Est.","+/- %PE","95% P.I."))%>%
  add_header_above(c(" "=3,"Including coefficient and interaction effect"=3,"Interaction effect with OOL only"=3,"Body depth as coefficient only"=3,
                     "Integer of head_length/body_depth"=3))%>%
  column_spec(1,italic=T)%>%
  column_spec(c(4,7,10,13),bold=T)%>%
  kable_styling()%>%
  scroll_box(width = "100%")
Table 11.1: Length estimates for specimens of Dunkleosteus terrelli using a model that incorporates relative body depth. All lengths in cm.
Including coefficient and interaction effect
Interaction effect with OOL only
Body depth as coefficient only
Integer of head_length/body_depth
Taxon Specimen Actual Length Est. +/- %PE 95% P.I. Est. +/- %PE 95% P.I. Est. +/- %PE 95% P.I. Est. +/- %PE 95% P.I.
Millerosteus minor Composite Millerosteus 14.9 15.9 (13.2-18.6) (10.2-24.5) 15.9 (13.2-18.5) (10.3-24.5) 15.9 (13.3-18.5) (10.3-24.5) 15.5 (13-18) (10.2-23.5)
Africanaspis dorissa Gess and Trinajstic 2017 23.0 24.5 (20.3-28.6) (15.8-37.9) 24.6 (20.5-28.8) (15.9-38) 24.6 (20.6-28.7) (16-37.9) 24.0 (20.1-27.9) (15.8-36.3)
Incisoscutum ritchei Recon. (Trinajstic 2013) 30.3 31.8 (26.4-37.2) (20.5-49.2) 32.0 (26.6-37.3) (20.7-49.4) 31.6 (26.4-36.8) (20.5-48.6) 30.7 (25.7-35.6) (20.3-46.4)
Coccosteus cuspidatus FMNH PF 1673 37.1 36.6 (30.4-42.8) (23.7-56.7) 36.8 (30.6-42.9) (23.8-56.8) 35.8 (29.9-41.7) (23.2-55) 34.9 (29.3-40.6) (23.1-52.9)
Coccosteus cuspidatus Recon. (M & W 1968) 39.4 44.4 (36.9-52) (28.7-68.8) 44.7 (37.1-52.2) (28.9-69) 44.0 (36.8-51.3) (28.6-67.7) 42.4 (35.5-49.2) (28-64.2)
Holonema westolli Recon. (Miles 1971) 60.6 51.2 (42.5-59.9) (33.1-79.3) 51.4 (42.7-60) (33.3-79.4) 49.6 (41.4-57.8) (32.2-76.3) 48.6 (40.8-56.5) (32.1-73.7)
Plourdosteus canadensis MNHM 2-177 37.5 52.0 (43.2-60.9) (33.6-80.5) 52.2 (43.4-61) (33.8-80.6) 51.2 (42.8-59.6) (33.3-78.8) 49.5 (41.5-57.5) (32.7-75)
Watsonosteus fletti NMS G.1995.4.2 56.6 65.8 (54.6-77) (42.5-101.9) 65.9 (54.8-77) (42.6-101.8) 64.2 (53.6-74.8) (41.7-98.8) 62.4 (52.3-72.5) (41.2-94.5)
Amazichthys trinajsticae AA.MEM.DS.8 89.7 78.3 (65-91.7) (50.6-121.3) 78.4 (65.2-91.6) (50.7-121.1) 76.3 (63.7-88.8) (49.6-117.4) 74.3 (62.3-86.3) (49.1-112.5)
Dunkleosteus terrelli CMNH 7424 NA 196.7 (163.1-230.2) (127-304.5) 194.3 (161.6-227) (125.7-300.3) 195.6 (163.4-227.8) (127.1-301) 188.9 (158.4-219.4) (124.7-286.1)
Dunkleosteus terrelli CMNH 6090 NA 282.7 (234.5-330.9) (182.3-438.5) 283.1 (235.4-330.7) (183-437.7) 288.9 (241.3-336.5) (187.7-444.6) 280.6 (235.3-326) (185.3-425.1)
Dunkleosteus terrelli CMNH 7054 NA 297.4 (246.7-348.1) (191.8-461.1) 296.1 (246.2-345.9) (191.5-457.8) 303.3 (253.4-353.3) (197.1-466.8) 294.0 (246.4-341.5) (194.1-445.3)
Dunkleosteus terrelli CMNH 5768 NA 323.7 (268.5-378.9) (207.8-504.3) 333.3 (277.2-389.4) (215.4-515.6) 342.6 (286.2-399.1) (222.6-527.5) 335.4 (281.1-389.6) (221.4-508)

12 Investigating effect of snout length on regression equation

12.1 Statistics of model including snout length

with_snout_length<-lm(log(total_length)~log(snout_length)+log(OOL),
                      data=data_final)
summary(with_snout_length)
## 
## Call:
## lm(formula = log(total_length) ~ log(snout_length) + log(OOL), 
##     data = data_final)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.53993 -0.11835  0.00076  0.10939  1.60679 
## 
## Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       2.123972   0.010160  209.06   <2e-16 ***
## log(snout_length) 0.230067   0.006753   34.07   <2e-16 ***
## log(OOL)          0.748199   0.008108   92.28   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1923 on 3166 degrees of freedom
##   (53 observations deleted due to missingness)
## Multiple R-squared:  0.9614, Adjusted R-squared:  0.9614 
## F-statistic: 3.947e+04 on 2 and 3166 DF,  p-value: < 2.2e-16
rbind("Without Snout Length"=regression.stats(fit.OOL),
      "With Snout Length"=regression.stats(with_snout_length))

From this it is very clear that among the sample of extant fishes, including snout length as an extra explanatory term in the model such that pre-orbital and OOL regions of the skull are allowed to scale independently has a better accuracy in estimating total length than just using OOL by itself.

12.2 Predicting length in arthrodires using snout length

fossil_taxa%>%
  filter(!is.na(snout_length),!!fossil.specimens,clade=="Placodermi")%>%
  mutate(estimated_length_with_snout=exp(predict(with_snout_length,.))*regression.stats(with_snout_length)$CF,
         estimated_length=exp(predict(fit.OOL,.))*regression.stats(fit.OOL)$CF)%>%
  mutate(PE_snout=(estimated_length_with_snout-total_length)/estimated_length_with_snout,
         PE=(estimated_length-total_length)/estimated_length)%>%
  rownames_to_column()%>%
  select(taxon,specimen,total_length,estimated_length,PE,estimated_length_with_snout,PE_snout)%>%
  kable(digits=c(1,1,1,1,3,1,3),align=c("l","c","c","c","c","c","c"),
        col.names = c("Taxon","Specimen","Total Length","Estimated Length","%PE","Estimated Length","%PE"),
        caption="Predicting length in whole-body arthrodire specimens with OOL including snout length as an additional variable")%>%
  add_header_above(c(" "=3,"Without Snout Length"=2,
                     "With Snout Length"=2))%>%
  column_spec(1,italic=T)%>%
  column_spec(c(4,6),bold=T)%>%
  kable_styling()
Table 12.1: Predicting length in whole-body arthrodire specimens with OOL including snout length as an additional variable
Without Snout Length
With Snout Length
Taxon Specimen Total Length Estimated Length %PE Estimated Length %PE
Millerosteus minor FMNH PF 1089 13.7 13.9 0.011 11.0 -0.247
Millerosteus minor Composite Millerosteus 14.9 16.0 0.068 14.4 -0.039
Africanaspis dorissa Gess and Trinajstic 2017 23.0 24.4 0.059 22.1 -0.040
Incisoscutum ritchei Recon. (Trinajstic 2013) 30.3 31.6 0.043 27.4 -0.104
Coccosteus cuspidatus NMS 1893.107.27 29.6 35.1 0.156 29.5 -0.003
Coccosteus cuspidatus NMS 1897.55.6 32.3 36.0 0.105 30.7 -0.051
Coccosteus cuspidatus FMNH PF 1673 37.1 36.5 -0.017 32.2 -0.152
Coccosteus cuspidatus Recon. (M & W 1968) 39.4 43.9 0.103 30.6 -0.289
Coccosteus cuspidatus NMS 1900.12.12 34.4 43.1 0.203 31.8 -0.080
Coccosteus cuspidatus ROM VP 52664 37.5 42.5 0.118 35.5 -0.056
Plourdosteus canadensis MNHM 2-177 37.5 51.4 0.270 39.8 0.058
Dickosteus threiplandi NMS 1987.7.118 43.7 56.1 0.221 37.9 -0.152
Holonema westolli Recon. (Miles 1971) 60.6 51.2 -0.185 45.8 -0.325
Watsonosteus fletti NMS G.1995.4.2 56.6 65.3 0.133 52.7 -0.074
Gen. et sp. nov. CMNH 50233 63.0 68.5 0.080 57.9 -0.087
Dickosteus threiplandi NHMUK PV P 49663 52.3 69.5 0.247 59.4 0.119
Amazichthys trinajsticae AA.MEM.DS.8 89.7 78.0 -0.150 62.5 -0.435
Dunkleosteus terrelli CMNH 7424 NA 195.2 NA 153.2 NA
Dunkleosteus terrelli CMNH 6090 NA 293.0 NA 227.4 NA
Dunkleosteus terrelli CMNH 7054 NA 305.7 NA 239.8 NA
Dunkleosteus terrelli CMNH 5768 NA 352.6 NA 272.0 NA

12.3 Relative snout length proportions in fishes

grid.arrange(nrow=1,
ggplot(data_final%>%
         drop_na(head_length,snout_length)%>%
         filter(clade!="Petromyzontiformes",
                order!="Blenniiformes"),
       aes(y=snout_length,x=head_length))+
  scale_x_continuous(trans="log10")+
  scale_y_continuous(trans="log10")+
  geom_star(aes(color=clade,starshape=clade))+
  geom_star(aes(fill=clade,starshape=clade))+
  geom_smooth(formula=y~x,method="lm")+
  geom_smooth(data=.%>%filter(clade=="Placodermi"),aes(color=clade),
              formula=y~x,method="lm")+
  labs(x="Head Length (mm)",y="Snout Length (mm)")+
  scale_starshape_manual(values=c(15,15,1,15))+
  scale_color_manual(values=c(NA,NA,hue_pal()(4)[3],NA),na.value=NA)+
  geom_star(aes(fill=clade,starshape=clade),
                         data=.%>%filter(clade=="Placodermi"),size=2)+
  ggtitle("Versus Head Length")+
  theme(legend.position="none"),
ggplot(data_final%>%
         drop_na(total_length,snout_length)%>%
         filter(!clade %in% c("Petromyzontiformes"),
                order!="Blenniiformes"),
       aes(y=snout_length,x=total_length))+
  scale_x_continuous(trans="log10")+
  scale_y_continuous(trans="log10")+
  geom_star(aes(color=clade,starshape=clade))+
  geom_star(aes(fill=clade,starshape=clade))+
  geom_smooth(formula=y~x,method="lm")+
  scale_starshape_manual(values=c(15,15,1,15))+
  geom_smooth(data=.%>%filter(clade=="Placodermi"),formula=y~x,method="lm",
              aes(color=clade))+
  geom_star(aes(fill=clade,starshape=clade),
                         data=.%>%filter(clade=="Placodermi"),size=2)+
  ggtitle("Versus Total Length")+
  scale_color_manual(values=c(NA,NA,hue_pal()(4)[3],NA),na.value=NA)+
  labs(x="Total Length (mm)",y="Snout Length (mm)",fill="Clade",
       color="Clade",starshape="Clade")+
  theme(legend.position=c(0.85,0.2)))
Graph of snout length versus head length (left) and total length (right), showing how arthrodire placoderms have significantly shorter snouts than extant fishes. Blenniiformes (in which snout length is near zero) omitted for visualization purposes. All axes here using a log10 scale.

Figure 12.1: Graph of snout length versus head length (left) and total length (right), showing how arthrodire placoderms have significantly shorter snouts than extant fishes. Blenniiformes (in which snout length is near zero) omitted for visualization purposes. All axes here using a log10 scale.

ggplot(data_final%>%
         filter(!is.na(total_length) & !is.na(OOL),
                length_as!="estimated t.l.")%>%
         mutate(residuals=fit.OOL$residuals)%>%
         filter(order!="Blenniiformes"),
   #Note: Blenniiformes was excluded for clarity because many blennies have eyes that are at the very anterior end of their skull, thus head_length==postorbital length or is very close, and when log-transformed this causes issues with plotting the results.
       aes(x=snout_length,y=residuals))+
  scale_x_continuous(trans="log10")+
  geom_star(aes(fill=clade,starshape=clade))+
  geom_star(data=.%>%filter(clade=="Placodermi"),
             aes(starshape=clade),fill="white",color="white",size=3,show.legend=F)+
  geom_star(data=.%>%filter(clade=="Placodermi"),size=2.5,
             aes(fill=clade,starshape=clade),show.legend=F)+
  scale_starshape_manual(values=c(15,13,11,1,28))+
  geom_smooth(formula=y~x,method="lm")+
  labs(x="Snout Length (mm)",y="Residuals of OOL Equation",color="Clade",fill="Clade",starshape="Clade")+
  theme(legend.position=c(0.15,0.8))
Graph of the residuals of the OOL equation against log-transformed snout length, showing how snout length has a slight allometric correlation with total length in fishes, but that the short snout of arthrodires does not appear to strongly influence their regression results. Note that Blenniiformes was excluded due to the fact that OOL and head length in these taxa are almost equal, which causes problems with log-transforming snout-length (i.e., snout_length=0).

Figure 12.2: Graph of the residuals of the OOL equation against log-transformed snout length, showing how snout length has a slight allometric correlation with total length in fishes, but that the short snout of arthrodires does not appear to strongly influence their regression results. Note that Blenniiformes was excluded due to the fact that OOL and head length in these taxa are almost equal, which causes problems with log-transforming snout-length (i.e., snout_length=0).

12.3.1 Scatter plot of percent snout length versus head length

grid.arrange(nrow=1,
  ggplot(data_final%>%filter(!is.na(snout_length),!is.na(head_length)),
       aes(x=head_length,
           y=snout_length/head_length))+
    ggtitle("A")+
    geom_star(aes(fill=clade),.%>%filter(clade!="Placodermi"),starshape=15)+
    geom_smooth(formula=y~x,method="lm",aes(color=clade),alpha=0.25)+
    scale_x_continuous(trans="log10")+
    geom_star(data=.%>%filter(clade=="Placodermi"),fill="white",color="white",size=3)+
    geom_star(data=.%>%filter(clade=="Placodermi"),aes(fill=clade),size=2.5)+
    labs(color="Clade",y="Percent Snout Length",x="Head Length (cm)")+
    scale_y_continuous(labels=scales::label_percent(accuracy = 1))+
    theme(legend.position="none"),
  ggplot(data_final%>%filter(!is.na(snout_length),!is.na(head_length)),
       aes(x=clade,
           y=snout_length/head_length))+
      ggtitle("B")+
    geom_hline(aes(yintercept=mean(snout_length/head_length)),linetype="dashed")+
    geom_violin(scale="width",aes(fill=clade))+
    geom_boxplot(width=0.4)+
    labs(fill="Clade",y="Snout Length As % Head Length",x="Clade")+
    scale_y_continuous(labels=scales::label_percent(accuracy = 1))+
    theme(legend.position=c(0.8,0.8),
          axis.text.x=element_text(angle = 45, vjust = 1, hjust=1)))
Scatter plot (A) and box-and-whisker plot (B) of snout length as a percentage of head length in fishes, showing how arthrodire placoderms have significantly shorter snouts than other groups.

Figure 12.3: Scatter plot (A) and box-and-whisker plot (B) of snout length as a percentage of head length in fishes, showing how arthrodire placoderms have significantly shorter snouts than other groups.

12.4 Percent snout length with species-average values

data_final%>%
  drop_na(snout_length,head_length)%>%
  group_by(taxon)%>%
  summarise(snout_length=mean(snout_length),
            head_length=mean(head_length),
            clade=unique(clade))%>%
  ggplot(aes(x=head_length,
             y=snout_length/head_length))+
  geom_star(aes(fill=clade,starshape=clade))+
  scale_starshape_manual(values=c(15,15,15,1,15))+
  geom_smooth(formula=y~x,method="lm",aes(color=clade),alpha=0.25)+
  geom_star(aes(fill=clade,starshape=clade),
            .%>%filter(clade=="Placodermi"),size=2.5)+
  scale_x_continuous(trans="log10")+
  scale_y_continuous(labels=scales::label_percent(accuracy = 1))+
  labs(color="Clade",y="Percent Snout Length",x="Head Length (cm)")

12.5 Model including snout length, with variable clade level

data_final%>%
  drop_na(total_length,snout_length,OOL)%>%
  group_by(taxon)%>%
  summarise(across(c(total_length,OOL,snout_length),~mean(.)),
            clade=unique(clade))%$%
  lm(log(total_length)~log(OOL)+log(snout_length)*clade-clade)%>%
  summary()
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) + log(snout_length) * 
##     clade - clade)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.49251 -0.12031 -0.01528  0.10390  1.64507 
## 
## Coefficients:
##                                           Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                                2.14344    0.01918 111.780  < 2e-16 ***
## log(OOL)                                   0.72853    0.01558  46.771  < 2e-16 ***
## log(snout_length)                          0.22709    0.01257  18.072  < 2e-16 ***
## log(snout_length):cladeChondrichthyes      0.03629    0.01008   3.602 0.000332 ***
## log(snout_length):cladePetromyzontiformes  0.02559    0.08246   0.310 0.756392    
## log(snout_length):cladePlacodermi          0.21507    0.12085   1.780 0.075457 .  
## log(snout_length):cladeSarcopterygii       0.04514    0.05773   0.782 0.434520    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2013 on 964 degrees of freedom
## Multiple R-squared:  0.9517, Adjusted R-squared:  0.9514 
## F-statistic:  3163 on 6 and 964 DF,  p-value: < 2.2e-16

ANOVA examining if snout proportions (as a percentage of total length) are consistent across fishes

data_final%>%
  drop_na(total_length,snout_length)%>%
  group_by(taxon)%>%
  summarise(across(c(total_length,snout_length),~mean(.)),
            clade=unique(clade))%>%
  mutate(percent_snout=snout_length/total_length)%>%
  lm(percent_snout~clade,.) %>% summary()
## 
## Call:
## lm(formula = percent_snout ~ clade, data = .)
## 
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -0.064330 -0.021019 -0.004309  0.013836  0.225219 
## 
## Coefficients:
##                          Estimate Std. Error t value Pr(>|t|)    
## (Intercept)              0.072032   0.001173  61.403  < 2e-16 ***
## cladeChondrichthyes     -0.004917   0.002688  -1.829 0.067652 .  
## cladePetromyzontiformes  0.003276   0.011561   0.283 0.776977    
## cladePlacodermi         -0.039715   0.010354  -3.836 0.000133 ***
## cladeSarcopterygii      -0.011592   0.018819  -0.616 0.538056    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.03253 on 966 degrees of freedom
## Multiple R-squared:  0.01816,    Adjusted R-squared:  0.01409 
## F-statistic: 4.467 on 4 and 966 DF,  p-value: 0.001399

ANOVA examining if snout proportions (as a percentage of head length) are consistent across fishes

data_final%>%
  drop_na(head_length,snout_length)%>%
  group_by(taxon)%>%
  summarise(across(c(head_length,snout_length),~mean(.)),
            clade=unique(clade))%>%
  mutate(percent_snout=snout_length/head_length)%>%
  lm(percent_snout~clade,.) %>% summary()
## 
## Call:
## lm(formula = percent_snout ~ clade, data = .)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.26808 -0.07084 -0.01686  0.05106  0.53431 
## 
## Coefficients:
##                          Estimate Std. Error t value Pr(>|t|)    
## (Intercept)              0.307864   0.003818  80.631  < 2e-16 ***
## cladeChondrichthyes     -0.005443   0.008747  -0.622    0.534    
## cladePetromyzontiformes  0.030406   0.037629   0.808    0.419    
## cladePlacodermi         -0.146713   0.020029  -7.325 4.96e-13 ***
## cladeSarcopterygii      -0.020117   0.061249  -0.328    0.743    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1059 on 985 degrees of freedom
## Multiple R-squared:  0.05254,    Adjusted R-squared:  0.04869 
## F-statistic: 13.66 on 4 and 985 DF,  p-value: 7.683e-11

Based on this, it appears that arthrodires, on average, have shorter snouts (preorbital length) than chondrichthyans, actinopterygians, lampreys, and sarcopterygians. Note the magnitude of the interaction coefficient for arthrodire is much greater than all other fish clades.

fit.snout_with_clade<-lm(log(total_length)~log(OOL)+log(snout_length)*clade-clade,
                         data_final)
fit.snout_with_clade2<-lm(log(total_length)~log(OOL)+log(snout_length)*clade,
                         data_final)
summary(fit.snout_with_clade)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) + log(snout_length) * 
##     clade - clade, data = data_final)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.51864 -0.11471 -0.00656  0.10149  1.62303 
## 
## Coefficients:
##                                            Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                                2.114605   0.010755 196.611  < 2e-16 ***
## log(OOL)                                   0.750201   0.008652  86.708  < 2e-16 ***
## log(snout_length)                          0.222808   0.006860  32.481  < 2e-16 ***
## log(snout_length):cladeChondrichthyes      0.014307   0.005065   2.825  0.00476 ** 
## log(snout_length):cladePetromyzontiformes  0.059586   0.007983   7.464 1.08e-13 ***
## log(snout_length):cladePlacodermi          0.018512   0.084207   0.220  0.82601    
## log(snout_length):cladeSarcopterygii      -0.002155   0.022507  -0.096  0.92373    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1907 on 3162 degrees of freedom
##   (53 observations deleted due to missingness)
## Multiple R-squared:  0.9621, Adjusted R-squared:  0.9621 
## F-statistic: 1.339e+04 on 6 and 3162 DF,  p-value: < 2.2e-16
rbind("Without Snout Length"=regression.stats(fit.OOL),
      "With Snout Length"=regression.stats(with_snout_length),
      "With Snout Length and Interaction Effect from Clade"=regression.stats(fit.snout_with_clade),
      "With Clade as Both Interaction Factor and Variable"=regression.stats(fit.snout_with_clade2))

The equation here is written as…

\[ \text{lm(log(total_length)~log(OOL)+log(snout_length)*clade-clade} \]

This is necessary because the variable of interest is the interaction between snout length and clade membership. I.e., the equation needs to compensate for the fact that arthrodires have shorter snouts than other fishes. If the term ‘-clade’ is not included, then clade by itself will be considered as an additional explanatory variable in the analysis.

summary(fit.snout_with_clade2)
## 
## Call:
## lm(formula = log(total_length) ~ log(OOL) + log(snout_length) * 
##     clade, data = data_final)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.51712 -0.10989 -0.00940  0.09613  1.62826 
## 
## Coefficients:
##                                            Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                                2.106300   0.010745 196.029  < 2e-16 ***
## log(OOL)                                   0.747107   0.008624  86.636  < 2e-16 ***
## log(snout_length)                          0.229552   0.006872  33.405  < 2e-16 ***
## cladeChondrichthyes                        0.147970   0.020886   7.085 1.71e-12 ***
## cladePetromyzontiformes                    0.122775   0.039255   3.128  0.00178 ** 
## cladePlacodermi                            0.141851   0.047082   3.013  0.00261 ** 
## cladeSarcopterygii                         0.637408   0.555843   1.147  0.25158    
## log(snout_length):cladeChondrichthyes     -0.051917   0.010709  -4.848 1.31e-06 ***
## log(snout_length):cladePetromyzontiformes -0.023348   0.028515  -0.819  0.41297    
## log(snout_length):cladePlacodermi         -0.026928   0.084682  -0.318  0.75051    
## log(snout_length):cladeSarcopterygii      -0.297530   0.259823  -1.145  0.25224    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1888 on 3158 degrees of freedom
##   (53 observations deleted due to missingness)
## Multiple R-squared:  0.9629, Adjusted R-squared:  0.9628 
## F-statistic:  8206 on 10 and 3158 DF,  p-value: < 2.2e-16

As seen here, the interaction between snout length and clade is no longer significant, but general clade membership in Placodermi is. This means the model is not accounting for variation driven by the shorter snouts of arthrodires, but is polarized by general residual variation between measured arthrodires and other fishes (except for snout length, since that is represented by the term log(snout_length):cladePlacodermi). This residual variation may not characterize Dunkleosteus, especially because it is largely driven by pattern in coccosteomorphs.

However, for the sake of transparency, the results of both are presented here.

fossil_taxa%>%
  filter(length_as=="total length"|specimen %in% c("CMNH 5768","CMNH 7424","CMNH 6090","CMNH 7054"))%>%
  augment(fit.snout_with_clade,newdata=.,interval="prediction")%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit1",.)))%>%
  augment(fit.snout_with_clade2,newdata=.,interval="prediction")%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit2",.)))%>%
  mutate(across(fit1.fitted:fit1.upper,~exp(.)*regression.stats(fit.snout_with_clade)$CF),
         across(fit2.fitted:fit2.upper,~exp(.)*regression.stats(fit.snout_with_clade2)$CF))%>%
  mutate(PE1_lower=fit1.fitted*(1-(regression.stats(fit.relativedepth1)$adjPE)/100),
         PE1_upper=fit1.fitted*(1+(regression.stats(fit.relativedepth1)$adjPE)/100),
         PE2_lower=fit2.fitted*(1-(regression.stats(fit.relativedepth2)$adjPE)/100),
         PE2_upper=fit2.fitted*(1+(regression.stats(fit.relativedepth2)$adjPE)/100),
         range1=paste0("(",round(fit1.lower,1),"-",round(fit1.upper,1),")"),
         range2=paste0("(",round(fit2.lower,1),"-",round(fit2.upper,1),")"),
         PE_range1=paste0("(",round(PE1_lower,1),"-",round(PE1_upper,1),")"),
         PE_range2=paste0("(",round(PE2_lower,1),"-",round(PE2_upper,1),")"))%>%
  select(taxon,specimen,total_length,fit1.fitted,PE_range1,range1,
         fit2.fitted,PE_range2,range2)%>%
  arrange(fit1.fitted)%>%
  kable(digits=1,align=c("l","c","c","c","c","c","c","c","c"),
        caption= "Length estimates for specimens of <i>Dunkleosteus terrelli</i> using a model that includes snout length but allows for snout length to vary by clade",
        col.names = c("Taxon","Specimen","Actual Length","Est.","+/- %PE.","95% P.I.",
                      "Est.","+/- %PE","95% P.I."))%>%
  column_spec(1, italic = T)%>%
  column_spec(c(3,4,7), bold = T)%>%
  add_header_above(c(" "=3,"Model with covarying term for clade only"=3,"Model with extra term for clade"=3))%>%
  kable_styling()
Table 12.2: Length estimates for specimens of Dunkleosteus terrelli using a model that includes snout length but allows for snout length to vary by clade
Model with covarying term for clade only
Model with extra term for clade
Taxon Specimen Actual Length Est. +/- %PE. 95% P.I. Est. +/- %PE 95% P.I.
Millerosteus minor FMNH PF 1089 13.7 10.8 (8.9-12.6) (7.1-16.5) 12.9 (10.7-15.1) (8.3-20)
Millerosteus minor Composite Millerosteus 14.9 14.2 (11.8-16.6) (9.7-20.8) 16.5 (13.8-19.3) (11.2-24.5)
Africanaspis dorissa Gess and Trinajstic 2017 23.0 22.0 (18.2-25.7) (15.1-31.9) 25.1 (20.8-29.3) (17.1-36.7)
Incisoscutum ritchei Recon. (Trinajstic 2013) 30.3 27.3 (22.6-31.9) (18.8-39.6) 31.0 (25.8-36.2) (21.2-45.3)
Coccosteus cuspidatus NMS 1893.107.27 29.6 29.4 (24.4-34.4) (20.2-42.7) 33.4 (27.8-39) (22.8-48.9)
Coccosteus cuspidatus Recon. (M & W 1968) 39.4 30.2 (25.1-35.4) (20.6-44.4) 35.1 (29.2-41) (23.7-52.1)
Coccosteus cuspidatus NMS 1897.55.6 32.3 30.6 (25.4-35.8) (21-44.5) 34.6 (28.8-40.5) (23.7-50.7)
Coccosteus cuspidatus NMS 1900.12.12 34.4 31.6 (26.2-36.9) (21.6-46) 36.3 (30.2-42.5) (24.7-53.5)
Coccosteus cuspidatus FMNH PF 1673 37.1 32.1 (26.7-37.6) (22-46.9) 36.2 (30.1-42.3) (24.7-53.1)
Coccosteus cuspidatus ROM VP 52664 37.5 35.4 (29.4-41.4) (24.3-51.6) 39.9 (33.2-46.7) (27.3-58.5)
Dickosteus threiplandi NMS 1987.7.118 43.7 37.6 (31.2-44) (25.7-55) 43.4 (36.1-50.7) (29.4-64.1)
Plourdosteus canadensis MNHM 2-177 37.5 39.6 (32.9-46.4) (27.3-57.7) 44.9 (37.4-52.5) (30.7-65.7)
Holonema westolli Recon. (Miles 1971) 60.6 45.9 (38.1-53.7) (31-68) 50.8 (42.2-59.3) (34.2-75.4)
Watsonosteus fletti NMS G.1995.4.2 56.6 52.8 (43.8-61.8) (35.9-77.6) 58.8 (48.9-68.6) (39.9-86.6)
Gen. et sp. nov. CMNH 50233 63.0 58.2 (48.3-68.1) (39.1-86.5) 64.1 (53.3-74.9) (43-95.5)
Dickosteus threiplandi NHMUK PV P 49663 52.3 59.7 (49.5-69.8) (40-89) 65.6 (54.6-76.7) (43.9-98)
Amazichthys trinajsticae AA.MEM.DS.8 89.7 62.7 (52-73.4) (42.4-92.8) 69.4 (57.7-81) (46.8-102.9)
Dunkleosteus terrelli CMNH 7424 NA 155.6 (129-182.1) (98.3-246.1) 165.6 (137.8-193.5) (105-261.4)
Dunkleosteus terrelli CMNH 6090 NA 232.2 (192.6-271.7) (141-382.4) 243.2 (202.3-284.1) (148.2-399)
Dunkleosteus terrelli CMNH 7054 NA 245.1 (203.3-286.8) (147.3-407.8) 255.8 (212.7-298.8) (154.4-423.8)
Dunkleosteus terrelli CMNH 5768 NA 278.3 (230.9-325.7) (165.7-467.4) 289.4 (240.7-338.1) (173.1-483.8)

Also note that despite including the extra term, some of the lengths are very far off from their actual values. Another issue is that by adding a term based on allometric relationships within Arthrodira, this is effectively unduly influencing the length estimates in Dunkleosteus by patterns in a small number of much smaller taxa, which may not necessarily be correct (see discussion in Ferrón et al., 2017). This is the entire problem that the use of OOL set out to avoid in the first place. Thus, snout_length does not appear to be a feasible variable to include in the model at this time.

13 Comparing length estimates across models using Coccosteus cuspidatus and CMNH 5768 (Dunkleosteus terrelli)

Code for fitting same models as above, but for species-averages.

predict.species_averages2<-(predict(fit.species_average2,fossil_taxa %>%
           drop_na(OOL,body_width,body_depth),
         interval="prediction")%>%
  exp()*regression.stats(fit.species_average2)$CF)%>%
  data.frame()%>%rownames_to_column()%>%
  arrange(fit)%>%
  left_join(fossil_taxa%>%rownames_to_column(),by="rowname")%>%
  filter(!!fossil.specimens)%>%
  select(taxon,specimen,fit,lwr,upr)

predict.species_averages<-(predict(fit.species_average,fossil_taxa %>%
           filter(!is.na(OOL),!is.na(body_width),!is.na(body_depth)),
         interval="prediction")%>%
  exp()*regression.stats(fit.species_average)$CF)%>%
  data.frame()%>%rownames_to_column()%>%
  arrange(fit)%>%
  left_join(fossil_taxa%>%rownames_to_column(),by="rowname")%>%
  filter(!!fossil.specimens)%>%
  select(taxon,specimen,fit,lwr,upr)

# Regression equation for shark-only model using species averages
fit.sharks_averages<-lm(log(total_length)~log(OOL),
                data_species%>%
                 filter(clade=="Chondrichthyes",
                        !order %in% c("Rajiformes","Chimaeriformes"),
                        genus!="Alopias"))

predict.sharks_averages<-(predict(fit.sharks_averages,
                                    fossil_taxa %>%
                                   drop_na(OOL,body_width,body_depth),
                                    interval="prediction")%>%
                              exp()*regression.stats(fit.sharks_averages)$CF)%>%
  data.frame()%>%rownames_to_column()%>%
  arrange(fit)%>%
  left_join(fossil_taxa%>%rownames_to_column(),by="rowname")%>%
  filter(!!fossil.specimens)%>%
  select(taxon,specimen,fit,lwr,upr)

# Regression equation for fusiform taxa using species averages
fit.fusiform_averages<-lm(log(total_length)~log(OOL),
                          data_species%>%
                            filter(shape %in% c("fusiform"),
                                   !family %in% c("Serranidae", "Monacanthidae",
                                                  "Holocentridae",
                                                  "Balistidae")))

# Model including shape, using species averages
fit.with_shape_averages<-lm(log(total_length)~log(OOL)+shape+(family=="Serranidae")+(family=="Holocentridae"),
                   data_species%>%filter(!family %in% c("Balistidae","Monacanthidae"),
                                         !genus %in% c("Regalecus","Rhinochimaera")))

predict.with_shape_averages<-(predict(fit.with_shape_averages,fossil_taxa,
                             interval="prediction")%>%
                       exp()*regression.stats(fit.with_shape)$CF)%>%
  data.frame()%>%
  rownames_to_column()%>%
  arrange(fit)%>%
  left_join(fossil_taxa%>%rownames_to_column(),by="rowname")%>%
  filter(!!fossil.specimens)%>%
  select(taxon,specimen,fit,lwr,upr)

# Model including shape and variable slope for Chondrichthyes, using species averages
fit.shape_clade4<-lm(log(total_length)~log(OOL)*(clade=="Chondrichthyes")+
                       shape+(family=="Serranidae")+(family=="Holocentridae"),
                    data_species)

# Model including body height as a covariate
fit.relativedepth_averages<-
  lm(log(total_length)~log(OOL)+I(log(head_length)/log(body_depth)),
     data_final%>%
       drop_na(total_length,OOL,body_depth,head_length)%>%
       group_by(taxon)%>%
       summarise(across(c(total_length,head_length,OOL,body_depth),mean),
                 across(c(clade,habitat,shape),unique))
     )

# Elongate and fusiform non-acanthopterygians only, species averages
fit.no_acanthopterygii2_averages<-data_species%>%
  filter(higher_group!="Acanthopterygii",
         shape %in% c("elongate","fusiform"))%$%
  lm(log(total_length)~log(OOL),.)

# Using head length, individual data
fit.head_length<-lm(log(total_length)~log(head_length),data_final)
# Using head length, speces_averages
fit.head_length_averages<-lm(log(total_length)~log(head_length),data_species)

Creating table

CMNH5768_lengths<-fossil_taxa%>%
  filter(specimen %in% c("CMNH 5768","Recon. (M & W 1968)"))%>%
  select(genus:length_as)%>%
  augment(fit.OOL,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.OOL)$CF),
         .PE_lower=.fitted*(1-(regression.stats(fit.OOL)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.OOL)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.OOL)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.OOL)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('All Species.individual_data', .)))%>%
  augment(fit.species_average,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.species_average)$CF),
         .PE_lower=.fitted*(1-(regression.stats(fit.species_average)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.species_average)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.species_average)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.species_average)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('All Species.species_averages', .)))%>%
# Excluding unusual body shapes
## Individual data
  augment(fit.no_extreme_shapes,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.no_extreme_shapes)$CF),
         .PE_lower=.fitted*(1-(regression.stats(fit.no_extreme_shapes)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.no_extreme_shapes)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.no_extreme_shapes)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.no_extreme_shapes)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('No Extreme Shapes.individual_data', .)))%>%
## Species averages
  augment(fit.species_average2,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.species_average2)$CF),
         .PE_lower=.fitted*(1-(regression.stats(fit.species_average2)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.species_average2)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.species_average2)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.species_average2)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('No Extreme Shapes.species_averages', .)))%>%
# With shape as a covariate
## Individual data
  augment(fit.with_shape,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.with_shape)$CF),
         .PE_lower=.fitted*(1-(regression.stats(fit.with_shape)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.with_shape)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.with_shape)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.with_shape)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('With Shape as a Covariate.individual_data', .)))%>%
## Species averages
  augment(fit.with_shape_averages,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.with_shape_averages)$CF),
         .PE_lower=.fitted*(1-(regression.stats(fit.with_shape_averages)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.with_shape_averages)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.with_shape_averages)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.with_shape_averages)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('With Shape as a Covariate.species_averages', .)))%>%
# Fusiform taxa only
## Fusiform taxa only, individual data
  augment(fit.fusiform,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.fusiform)$CF),
         .PE_lower=.fitted*(1-(regression.stats(fit.fusiform)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.fusiform)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.fusiform)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.fusiform)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('Fusiform Taxa Only.individual_data', .)))%>%
## Fusiform taxa only, species averages
  augment(fit.fusiform_averages,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.fusiform_averages)$CF),
         .PE_lower=.fitted*(1-(regression.stats(fit.fusiform_averages)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.fusiform_averages)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.fusiform_averages)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.fusiform_averages)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('Fusiform Taxa Only.species_averages', .)))%>%
# Including body height as a covariate
## Individual data
  augment(fit.relativedepth4,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.relativedepth4)$CF),
         .PE_lower=.fitted*(1-(regression.stats(fit.relativedepth4)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.relativedepth4)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.relativedepth4)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.relativedepth4)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('With Body Height as Covariate.individual_data', .)))%>%
## Species averages
  augment(fit.relativedepth_averages,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.relativedepth_averages)$CF+snout_length),
         .PE_lower=.fitted*(1-(regression.stats(fit.relativedepth_averages)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.relativedepth_averages)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.relativedepth_averages)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.relativedepth_averages)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('With Body Height as Covariate.species_averages', .)))%>%
# Treating snout length as an integer
## Individual data
  augment(fit.minus_snout_length1,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.minus_snout_length1)$CF+snout_length),
         .PE_lower=.fitted*(1-(regression.stats(fit.minus_snout_length1)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.minus_snout_length1)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.minus_snout_length1)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.minus_snout_length1)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('Treating Snout Length as Separate Integer.individual_data', .)))%>%
## Species averages
  augment(fit.minus_snout_length2,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.minus_snout_length2)$CF+snout_length),
         .PE_lower=.fitted*(1-(regression.stats(fit.minus_snout_length2)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.minus_snout_length2)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.minus_snout_length2)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.minus_snout_length2)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('Treating Snout Length as Separate Integer.species_averages', .)))%>%
# Pelagic taxa only
## Individual data
  augment(fit.pelagic,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.pelagic)$CF),
         .PE_lower=.fitted*(1-(regression.stats(fit.pelagic)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.pelagic)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.pelagic)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.pelagic)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('Pelagic Taxa Only.individual_data', .)))%>%
## Species averages
  augment(fit.pelagic_averages,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.pelagic_averages)$CF),
         .PE_lower=.fitted*(1-(regression.stats(fit.pelagic_averages)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.pelagic_averages)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.pelagic_averages)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.pelagic_averages)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('Pelagic Taxa Only.species_averages', .)))%>%
# Excluding acanthopterygian taxa only
## Individual data
  augment(fit.no_acanthopterygii2,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.no_acanthopterygii2)$CF),
         .PE_lower=.fitted*(1-(regression.stats(fit.no_acanthopterygii2)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.no_acanthopterygii2)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.no_acanthopterygii2)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.no_acanthopterygii2)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('Non-Acanthopterygians Only.individual_data', .)))%>%
## Species averages
  augment(fit.no_acanthopterygii2_averages,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.no_acanthopterygii2_averages)$CF),
         .PE_lower=.fitted*(1-(regression.stats(fit.no_acanthopterygii2_averages)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.no_acanthopterygii2_averages)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.no_acanthopterygii2_averages)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.no_acanthopterygii2_averages)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('Non-Acanthopterygians Only.species_averages', .)))%>%
# Shark taxa only
## Individual data
  augment(fit.sharks,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.sharks)$CF),
         .PE_lower=.fitted*(1-(regression.stats(fit.sharks)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.sharks)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.sharks)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.sharks)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('Sharks Only.individual_data', .)))%>%
## Species averages
  augment(fit.sharks_averages,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.sharks_averages)$CF),
         .PE_lower=.fitted*(1-(regression.stats(fit.sharks_averages)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.sharks_averages)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.sharks_averages)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.sharks_averages)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('Sharks Only.species_averages', .)))%>%
# With shape as a covariate and variable slopes for Chondrichthyes and all other fishes
## Individual data
  augment(fit.shape_clade3,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.shape_clade3)$CF),
         .PE_lower=.fitted*(1-(regression.stats(fit.shape_clade3)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.shape_clade3)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.shape_clade3)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.shape_clade3)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('Body Shape and Variable Slope for Chondrichthyes.individual_data', .)))%>%
## Species averages
  augment(fit.shape_clade4,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.shape_clade4)$CF),
         .PE_lower=.fitted*(1-(regression.stats(fit.shape_clade4)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.shape_clade4)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.shape_clade4)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.shape_clade4)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('Body Shape and Variable Slope for Chondrichthyes.species_averages', .)))%>%
# Using head length
## Individual data
  augment(fit.head_length,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.head_length)$CF),
         .PE_lower=.fitted*(1-(regression.stats(fit.head_length)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.head_length)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.head_length)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.head_length)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('Head Length.individual_data', .)))%>%
## Species averages
  augment(fit.head_length_averages,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.head_length_averages)$CF),
         .PE_lower=.fitted*(1-(regression.stats(fit.head_length_averages)$adjPE)/100),
         .PE_upper=.fitted*(1+(regression.stats(fit.head_length_averages)$adjPE)/100),
         .PE=paste0("(",
                    .fitted*(1-(regression.stats(fit.head_length_averages)$adjPE)/100),
                    "-",
                    .fitted*(1+(regression.stats(fit.head_length_averages)$adjPE)/100),
                    ")"))%>%
  rename_at(vars(starts_with('.')), funs(paste0('Head Length.species_averages', .)))%>%
  select(genus,
         "All Species.individual_data.fitted":last_col(),
         -ends_with(c("rownames","resid","PE")))%>%
  pivot_longer(cols=!genus,
               names_to = c("model","type","data_value"),
               names_sep = "\\.")%>%
  mutate(name=paste0(genus,".",type,".",data_value))%>%
  pivot_wider(id_cols="model")%>%
  mutate(Coccosteus.individual_data.PE=paste0("(",
                                              sprintf("%.1f",
                                               round(Coccosteus.individual_data.PE_lower,1)),
                                               "–",
                                              sprintf("%.1f",
                                               round(Coccosteus.individual_data.PE_upper,1)),
                                               ")"),
         Coccosteus.species_averages.PE=paste0("(",
                                               sprintf("%.1f",
                                               round(Coccosteus.species_averages.PE_lower,1)),
                                               "–",
                                               sprintf("%.1f",
                                               round(Coccosteus.species_averages.PE_upper,1)),
                                               ")"),
         Dunkleosteus.individual_data.PE=paste0("(",
           sprintf("%.1f",round(Dunkleosteus.individual_data.PE_lower,1)),
           "–",
           sprintf("%.1f",round(Dunkleosteus.individual_data.PE_upper,1)),
           ")"),
         Dunkleosteus.species_averages.PE=paste0("(",
           sprintf("%.1f",round(Dunkleosteus.species_averages.PE_lower,1)),
           "–",
           sprintf("%.1f",round(Dunkleosteus.species_averages.PE_upper,1)),
           ")"),
         Coccosteus.individual_data.range=paste0("(",
           sprintf("%.1f",round(Coccosteus.individual_data.lower,1)),
           "–",
           sprintf("%.1f",round(Coccosteus.individual_data.upper,1)),
           ")"),
         Coccosteus.species_averages.range=paste0("(",
           sprintf("%.1f",round(Coccosteus.species_averages.lower,1)),
           "–",
           sprintf("%.1f",round(Coccosteus.species_averages.upper,1)),
           ")"),
         Dunkleosteus.individual_data.range=paste0("(",
           sprintf("%.1f",round(Dunkleosteus.individual_data.lower,1)),
           "–",
           sprintf("%.1f",round(Dunkleosteus.individual_data.upper,1)),
           ")"),
         Dunkleosteus.species_averages.range=paste0("(",
           sprintf("%.1f",round(Dunkleosteus.species_averages.lower,1)),
           "–",
           sprintf("%.1f",round(Dunkleosteus.species_averages.upper,1)),
           ")"))%>%
  select(model,
         Dunkleosteus.individual_data.fitted,Dunkleosteus.individual_data.PE,
         Dunkleosteus.individual_data.range,
         Dunkleosteus.species_averages.fitted,Dunkleosteus.species_averages.PE,
         Dunkleosteus.species_averages.range,
         Coccosteus.individual_data.fitted,Coccosteus.individual_data.PE,
         Coccosteus.individual_data.range,
         Coccosteus.species_averages.fitted,Coccosteus.species_averages.PE,
         Coccosteus.species_averages.range)
# Exporting table for later use
write.xlsx(CMNH5768_lengths,
          "Devonian Fish Tale Table 3 (CMNH 5768 Lengths).xlsx",
          fileEncoding="UTF-8")
# Creating kable for display
CMNH5768_lengths%>%
  kable(digits=1,
        caption="Comparison of length estimates in <i>Dunkleosteus terrelli</i> (CMNH 5768) and <i>Coccosteus cuspidatus</i> (using the reconstruction in Miles and Westoll 1968) using a variety of different models",
        col.names=c("Model",
                    "Est.","+/- %PE","95% P.I.",
                    "Est.","+/- %PE","95% P.I.",
                    "Est.","+/- %PE","95% P.I.",
                    "Est.","+/- %PE","95% P.I."))%>%
  column_spec(c(2,5,8,11), bold = T)%>%
  add_header_above(c(" "=1,"Individual Data"=3,
                     "Species Averages"=3,
                     "Individual Data"=3,
                     "Species Averages"=3))%>%
  add_header_above(c(" "=1,"Dunkleosteus terrelli (CMNH 5768)"=6,
                     "Coccosteus cuspidatus (M & W 1968)"=6))%>%
  kable_styling()
Table 13.1: Comparison of length estimates in Dunkleosteus terrelli (CMNH 5768) and Coccosteus cuspidatus (using the reconstruction in Miles and Westoll 1968) using a variety of different models
Dunkleosteus terrelli (CMNH 5768)
Coccosteus cuspidatus (M & W 1968)
Individual Data
Species Averages
Individual Data
Species Averages
Model Est. +/- %PE 95% P.I. Est. +/- %PE 95% P.I. Est. +/- %PE 95% P.I. Est. +/- %PE 95% P.I.
All Species 352.6 (290.7–414.5) (226.8–548.1) 338.9 (278.4–399.4) (214.0–536.7) 43.9 (36.2–51.6) (28.3–68.3) 43.5 (35.7–51.3) (27.5–68.8)
No Extreme Shapes 353.8 (299.8–407.8) (241.7–518.0) 343.0 (289.9–396.1) (229.7–512.1) 45.3 (38.4–52.2) (30.9–66.3) 44.5 (37.6–51.4) (29.8–66.3)
With Shape as a Covariate 324.8 (285.8–363.9) (237.8–443.7) 320.1 (279.8–360.3) (229.9–445.6) 41.8 (36.8–46.8) (30.6–57.1) 42.1 (36.8–47.4) (30.3–58.5)
Fusiform Taxa Only 319.7 (281.7–357.6) (236.1–432.8) 313.9 (278.6–349.3) (234.1–421.1) 42.0 (37.0–46.9) (31.0–56.8) 41.9 (37.2–46.6) (31.3–56.2)
With Body Height as Covariate 335.4 (281.1–389.6) (221.4–508.0) 344.1 (283.6–404.6) (221.8–536.6) 42.4 (35.5–49.2) (28.0–64.2) 43.8 (36.1–51.5) (28.1–68.5)
Treating Snout Length as Separate Integer 336.8 (284.9–388.7) (231.1–492.7) 328.5 (276.1–380.9) (219.8–493.2) 42.3 (35.8–48.8) (28.8–62.1) 41.8 (35.1–48.5) (27.8–63.0)
Pelagic Taxa Only 357.5 (298.3–416.7) (242.4–527.2) 328.8 (276.7–380.9) (222.4–486.1) 47.3 (39.5–55.1) (32.1–69.7) 45.6 (38.4–52.8) (31.0–67.1)
Non-Acanthopterygians Only 340.5 (285.1–395.9) (225.7–513.7) 318.5 (279.3–357.7) (234.0–433.5) 43.0 (36.0–49.9) (28.5–64.8) 44.0 (38.5–49.4) (32.3–59.7)
Sharks Only 298.5 (264.2–332.9) (224.1–397.8) 299.6 (268.0–331.2) (227.9–393.9) 46.9 (41.5–52.3) (35.2–62.5) 46.9 (41.9–51.8) (35.7–61.5)
Body Shape and Variable Slope for Chondrichthyes 340.7 (298.4–382.9) (245.1–473.6) 328.6 (284.4–372.9) (226.9–476.0) 42.7 (37.4–48.0) (30.7–59.4) 42.6 (36.9–48.3) (29.5–61.6)
Head Length 266.7 (228.3–305.0) (184.2–386.0) 262.3 (221.3–303.2) (176.0–390.9) 32.7 (28.0–37.4) (22.6–47.4) 32.8 (27.7–38.0) (22.0–48.9)

The reason why some of the estimates using individual data seem to be high (3.7-3.8 m) appears to be directly due to including a very large sample of Kajikia albida (57 specimens, Robins 1974) and Tetrapturus spp. (~11 specimens of T. pfluegeri and ~36 individuals of T. belone; Robins and de Sylva 1963). These fishes all have rather elongate bodies, and the sheer number of individuals being counted results in the estimates being biased upwards when they are considered. This can be seen in the fact that estimates for Coccosteus are very consistent across individual specimen and species-average models, and in earlier iterations of the database prior to the addition of data from Robins (1974) the results between individual data and species-average models were nearly identical for Dunkleosteus terrelli.

14 Estimating length of the largest specimens of Dunkleosteus terrelli (CMNH 5936)

14.1 Examining correlation between biting portion of the inferognathal and orbit-opercular length

# Reading in Dunkleosteus measurement data
dunkleosteus_jaws<-read.csv("Devonian Fish Tale Supplementary File 2 (Dunkleosteus Measurements).csv",
                            header=T)%>%
  rename("OOL"="OOL")%>%
  mutate(clade="Placodermi",higher_group="Arthrodira",order="Arthrodira",
         family="Dunkleosteidae",habitat="pelagic",shape="fusiform")%>%
  augment(fit.shape_clade3,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.shape_clade3)$CF))%>%
  rename(fit=.fitted,lwr=.lower,upr=.upper)%>%
  select(specimen,JM1,JM2,JM3,JM4,JM5,inferognathal_length,OOL,head_length,
         fit,lwr,upr)

# Printing Dunkleosteus measurement data
dunkleosteus_jaws%>%
  remove_rownames()%>%column_to_rownames("specimen")%>%
  arrange(JM5)%>%
  select(-c(fit,lwr,upr))%>%
  kable(digits=1,align="c",
        caption="Skeletal measurements of the specimens of <i>Dunkleosteus terrelli</i> considered here. All measurements in cm.",
        col.names=c("JM1","JM2","JM3","JM4","JM5","Inferognathal Length","OOL","Head Length"))%>%
  kable_styling()
Table 14.1: Skeletal measurements of the specimens of Dunkleosteus terrelli considered here. All measurements in cm.
JM1 JM2 JM3 JM4 JM5 Inferognathal Length OOL Head Length
CMNH 8982 5.9 2.7 4.6 5.0 8.8 18.0 23.7 28.8
CMNH 50322 4.8 3.3 5.6 5.8 9.5 20.2 21.5 25.8
CMNH 6194 (right) 4.9 3.4 5.5 5.5 11.0 22.6 25.5 29.0
CMNH 6194 (left) 6.5 3.7 5.2 5.8 11.0 23.1 25.5 29.0
CMNH 7424 6.4 4.1 7.0 6.9 12.2 29.9 29.0 34.0
CMNH 5007 6.2 5.3 7.2 7.3 13.4 28.4 31.1 NA
CMNH OC 1421 8.5 5.0 9.9 9.8 18.3 39.1 36.8 46.0
CMNH 5740 NA NA 11.0 NA 20.4 40.9 41.0 48.4
CMNH 6090 (left) 10.7 5.7 11.8 11.5 20.4 41.3 43.6 51.0
CMNH 6090 (right) 11.2 4.6 12.4 11.9 21.1 43.5 43.6 51.0
CMNH 7054 (right) 9.2 7.8 13.4 14.5 22.8 47.0 45.5 53.6
CMNH 7054 (left) 9.7 7.6 12.9 13.9 23.7 48.4 45.5 53.6
CMNH 8131 9.8 7.8 13.4 13.6 24.2 50.7 45.8 56.5
CMNH 5768 (left) 13.3 9.8 15.9 13.2 26.2 54.1 52.5 61.3
CMNH 5768 (right) 13.2 8.5 14.8 14.5 28.1 55.5 52.5 61.3
CMNH 5936 12.8 10.8 18.6 20.4 33.9 NA NA NA

Note that CMNH 5768 was included twice because there is some taphonomic distortion between the left and right inferognathal, which could potentially bias estimates of CMNH 5936 if not accounted for as CMNH 5768 is the largest complete individual in this study by a large margin.

14.1.1 JM1

summary(lm(OOL~JM1,dunkleosteus_jaws))
## 
## Call:
## lm(formula = OOL ~ JM1, data = dunkleosteus_jaws)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -4.3218 -1.9645 -0.9268  2.2916  6.1508 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   6.0613     2.9324   2.067    0.061 .  
## JM1           3.6281     0.3241  11.195 1.04e-07 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 3.38 on 12 degrees of freedom
##   (2 observations deleted due to missingness)
## Multiple R-squared:  0.9126, Adjusted R-squared:  0.9053 
## F-statistic: 125.3 on 1 and 12 DF,  p-value: 1.045e-07
JM1_allometry<-lm(log(JM1)~log(OOL),dunkleosteus_jaws)
dunkleosteus_jaws%>%
  drop_na(OOL,JM1)%>%
  ggplot(aes(y=OOL,x=JM1))+
  geom_smooth(method="lm",formula=y~x)+
  geom_point(fill="black",color="white",shape=21,size=3)+
  labs(x="Length Between Medial and Posterior Cusp (cm)",
       y="Orbit-Opercular Length (cm)")+
  theme_classic()+
  geom_text(aes(label=specimen,
                x=ifelse(specimen %in% c("CMNH 5768 (left)","CMNH 7054 (right)"),JM1-0.25,
                         ifelse(specimen %in% c("CMNH 6090 (right)"),JM1+0.25,JM1)),
                y=ifelse(specimen %in% c("CMNH 5768 (left)","CMNH 6090 (right)"),OOL+1.5,
                         ifelse(specimen %in% c("CMNH 7054 (right)"),OOL-1.5,OOL)),
                hjust=ifelse(specimen %in% c("CMNH 5768 (left)","CMNH 7054 (right)",
                                             "CMNH 7054 (left)"),1,
                             ifelse(specimen %in% c("CMNH 6090 (right)","CMNH 8131"),0,0.5))),
            nudge_y=ifelse(residuals(lm(OOL~JM1,dunkleosteus_jaws%>%drop_na(JM1,OOL)))<0,-1,1))
Plot of JM1 versus orbit-opercular length in specimens of Dunkleosteus terrelli for which both measurements are known. Length between medial and posterior cusp to JM1 in Ferrón et al. (2017).

Figure 14.1: Plot of JM1 versus orbit-opercular length in specimens of Dunkleosteus terrelli for which both measurements are known. Length between medial and posterior cusp to JM1 in Ferrón et al. (2017).

14.1.2 JM2

summary(lm(OOL~JM2,dunkleosteus_jaws))
## 
## Call:
## lm(formula = OOL ~ JM2, data = dunkleosteus_jaws)
## 
## Residuals:
##    Min     1Q Median     3Q    Max 
## -5.112 -2.618 -1.183  1.720 11.187 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  12.0435     3.3710   3.573  0.00383 ** 
## JM2           4.4671     0.5566   8.026 3.64e-06 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 4.531 on 12 degrees of freedom
##   (2 observations deleted due to missingness)
## Multiple R-squared:  0.843,  Adjusted R-squared:  0.8299 
## F-statistic: 64.41 on 1 and 12 DF,  p-value: 3.639e-06
JM2_allometry<-lm(log(JM2)~log(OOL),dunkleosteus_jaws)
dunkleosteus_jaws%>%
  drop_na(OOL,JM2)%>%
  ggplot(aes(y=OOL,x=JM2))+
  geom_smooth(method="lm",formula=y~x)+
  geom_point(fill="black",color="white",shape=21,size=3)+
  labs(x="Length Between Symphyseal and Accessory Cusp (cm)",
       y="Orbit-Opercular Length (cm)")+
  theme_classic()+
  geom_text(aes(label=specimen,
                y=ifelse(specimen %in% c("CMNH 8131","CMNH 7054 (left)","CMNH 6194 (right)"),
                         OOL+2,OOL),
                x=ifelse(specimen %in% c("CMNH 8131","CMNH 7054 (right)"),JM2+0.1,JM2),
                hjust=ifelse(specimen %in% c("CMNH 8131","CMNH 7054 (right)","CMNH 6194 (left)"),0,
                             ifelse(specimen %in% c("CMNH 7054 (left)","CMNH 6194 (right)",
                                                    "CMNH 6090 (right)"),1,0.5))),
            nudge_y=ifelse(residuals(lm(OOL~JM2,dunkleosteus_jaws%>%drop_na(JM2,OOL)))<0,-1,1))
Plot of JM2 versus orbit-opercular length in specimens of Dunkleosteus terrelli for which both measurements are known. Length between symphyseal cusp and accessory cusp is JM2 in Ferrón et al. (2017).

Figure 14.2: Plot of JM2 versus orbit-opercular length in specimens of Dunkleosteus terrelli for which both measurements are known. Length between symphyseal cusp and accessory cusp is JM2 in Ferrón et al. (2017).

14.1.3 JM3

summary(lm(OOL~JM3,dunkleosteus_jaws))
## 
## Call:
## lm(formula = OOL ~ JM3, data = dunkleosteus_jaws)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -3.9120 -0.6777  0.0452  1.1049  2.0653 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  10.0211     1.1343   8.834 7.43e-07 ***
## JM3           2.7362     0.1058  25.863 1.45e-12 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.523 on 13 degrees of freedom
##   (1 observation deleted due to missingness)
## Multiple R-squared:  0.9809, Adjusted R-squared:  0.9795 
## F-statistic: 668.9 on 1 and 13 DF,  p-value: 1.451e-12
fit.JM3<-lm(OOL~JM3,dunkleosteus_jaws)
JM3_allometry<-lm(log(OOL)~log(JM3),dunkleosteus_jaws)
dunkleosteus_jaws%>%
  filter(!is.na(OOL))%>%
  ggplot(aes(y=OOL,x=JM3))+
  geom_smooth(method="lm",formula=y~x)+
  geom_point(fill="black",color="white",shape=21,size=3)+
  labs(x="Height of Inferognathal at Posterior Cusp (cm)",
       y="Orbit-Opercular Length (cm)")+
  theme_classic()+
  geom_text(aes(x=ifelse(specimen %in% c("CMNH 8131"),JM3+0.5,JM3),
                y=ifelse(specimen %in% c("CMNH 8131","CMNH 6194 (right)"),OOL+1,OOL),
                label=specimen,
                hjust=ifelse(specimen %in% c("CMNH 5768 (left)","CMNH 6194 (right)"),0.5,
                       ifelse(residuals(lm(OOL~JM3,dunkleosteus_jaws%>%drop_na(JM3,OOL)))<0,0,1))),
            nudge_y=ifelse(residuals(lm(OOL~JM3,dunkleosteus_jaws%>%drop_na(JM3,OOL)))<0,-1,1))
Plot of JM3 versus orbit-opercular length in specimens of Dunkleosteus terrelli for which both measurements are known. Height of the inferognathal at posterior cusp is JM3 in Ferrón et al. (2017).

Figure 14.3: Plot of JM3 versus orbit-opercular length in specimens of Dunkleosteus terrelli for which both measurements are known. Height of the inferognathal at posterior cusp is JM3 in Ferrón et al. (2017).

14.1.4 JM4

summary(lm(OOL~JM4,dunkleosteus_jaws))
## 
## Call:
## lm(formula = OOL ~ JM4, data = dunkleosteus_jaws)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -4.6679 -1.4871  0.3709  1.1684  5.9372 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   8.9464     2.2311    4.01  0.00173 ** 
## JM4           2.8476     0.2108   13.51 1.28e-08 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 2.839 on 12 degrees of freedom
##   (2 observations deleted due to missingness)
## Multiple R-squared:  0.9383, Adjusted R-squared:  0.9332 
## F-statistic: 182.5 on 1 and 12 DF,  p-value: 1.278e-08
JM4_allometry<-lm(log(JM4)~log(OOL),dunkleosteus_jaws)
dunkleosteus_jaws%>%
  drop_na(OOL,JM4)%>%
  ggplot(aes(y=OOL,x=JM4))+
  geom_smooth(method="lm",formula=y~x)+
  geom_point(fill="black",color="white",shape=21,size=3)+
  labs(x="Height of Inferognathal at Accessory Cusps (cm)",
       y="Orbit-Opercular Length (cm)")+
  theme_classic()+
  geom_text(aes(label=specimen,
                x=ifelse(specimen %in% c("CMNH 6090 (left)"),JM4-0.25,JM4),
                y=ifelse(specimen %in% c("CMNH 6090 (left)"),OOL-1,
                         ifelse(specimen %in% c("CMNH 7054 (left)"),OOL+2.5,OOL)),
                hjust=ifelse(specimen %in% c("CMNH 6090 (left)",
                                             "CMNH 5768 (left)"),1,0.5)),
            nudge_y=ifelse(residuals(lm(OOL~JM4,dunkleosteus_jaws%>%drop_na(JM4,OOL)))<0,-1,1))
Plot of JM4 versus orbit-opercular length in specimens of Dunkleosteus terrelli for which both measurements are known. Biting portion of the inferognathal is JM4 in Ferrón et al. (2017).

Figure 14.4: Plot of JM4 versus orbit-opercular length in specimens of Dunkleosteus terrelli for which both measurements are known. Biting portion of the inferognathal is JM4 in Ferrón et al. (2017).

14.1.5 JM5

fit.JM5<-lm(OOL~JM5,dunkleosteus_jaws)
summary(fit.JM5)
## 
## Call:
## lm(formula = OOL ~ JM5, data = dunkleosteus_jaws)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -2.2666 -1.1650 -0.2614  1.1925  2.3224 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  8.33726    1.13083   7.373 5.40e-06 ***
## JM5          1.61551    0.05907  27.348 7.11e-13 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.442 on 13 degrees of freedom
##   (1 observation deleted due to missingness)
## Multiple R-squared:  0.9829, Adjusted R-squared:  0.9816 
## F-statistic: 747.9 on 1 and 13 DF,  p-value: 7.111e-13
JM5_allometry<-lm(log(JM5)~log(OOL),dunkleosteus_jaws)
dunkleosteus_jaws%>%
  filter(!is.na(OOL))%>%
  ggplot(aes(y=OOL,x=JM5))+
  geom_smooth(method="lm",formula=y~x)+
  geom_point(fill="black",color="white",shape=21,size=3)+
  labs(x="Length of Biting Portion of Inferognathal (cm)",
       y="Orbit-Opercular Length (cm)")+
  theme_classic()+
  geom_text(aes(label=specimen,
                x=ifelse(specimen %in% c("CMNH 8131","CMNH 6194 (right)"),JM5+0.5,
                         ifelse(specimen %in% ("CMNH 6090 (left)"),JM5-0.5,JM5)),
                y=ifelse(specimen %in% c("CMNH 8131"),OOL+1,
                         ifelse(specimen %in% c("CMNH 6194 (right)"),OOL+2,
                         ifelse(specimen %in% c("CMNH 6090 (left)"),OOL-1,OOL))),
                hjust=ifelse(specimen %in% c("CMNH 8131","CMNH 7054 (left)","CMNH 6194 (left)"),0,
                             ifelse(specimen %in% c("CMNH 6090 (left)","CMNH 7054 (right)",
                                                    "CMNH 5768 (left)"),1,0.5))
                ),nudge_y=ifelse(residuals(lm(OOL~JM5,dunkleosteus_jaws%>%drop_na(JM5,OOL))) < 0, -1, 1))
Plot of JM5 versus orbit-opercular length in specimens of Dunkleosteus terrelli for which both measurements are known. Length of biting portion of the inferognathal is JM5 in Ferrón et al. (2017).

Figure 14.5: Plot of JM5 versus orbit-opercular length in specimens of Dunkleosteus terrelli for which both measurements are known. Length of biting portion of the inferognathal is JM5 in Ferrón et al. (2017).

14.1.6 Inferognathal length

inferognathal_length<-lm(OOL~inferognathal_length,dunkleosteus_jaws)
summary(inferognathal_length)
## 
## Call:
## lm(formula = OOL ~ inferognathal_length, data = dunkleosteus_jaws)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -2.5587 -1.4128  0.1857  1.0839  2.9929 
## 
## Coefficients:
##                      Estimate Std. Error t value Pr(>|t|)    
## (Intercept)           6.77020    1.42844    4.74 0.000387 ***
## inferognathal_length  0.82009    0.03616   22.68 7.73e-12 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.732 on 13 degrees of freedom
##   (1 observation deleted due to missingness)
## Multiple R-squared:  0.9753, Adjusted R-squared:  0.9735 
## F-statistic: 514.4 on 1 and 13 DF,  p-value: 7.729e-12
inferognathal_length_allometry<-lm(log(inferognathal_length)~log(OOL),dunkleosteus_jaws)
dunkleosteus_jaws%>%
  filter(!is.na(OOL))%>%
  ggplot(aes(y=OOL,x=inferognathal_length))+
  geom_smooth(method="lm",formula=y~x)+
  geom_point(fill="black",color="white",shape=21,size=3)+
  labs(x="Inferognathal Length (cm)",
       y="Orbit-Opercular Length (cm)")+
  theme_classic()+
  geom_text(aes(x=ifelse(specimen %in% c("CMNH 5768 (left)","CMNH 6090 (left)"),
                         inferognathal_length-0.5,
                         ifelse(specimen %in% c("CMNH 8131"),
                         inferognathal_length+0.5,
                         inferognathal_length)),
                y=ifelse(specimen %in% c("CMNH 6090 (left)","CMNH 5768 (left)"),OOL-1,
                         ifelse(specimen %in% c("CMNH 8131"),OOL+1,OOL)),
                label=specimen,
                hjust=ifelse(specimen %in% c("CMNH 8131","CMNH 7054 (left)","CMNH 6194 (left)"),0,
                             ifelse(specimen %in% c("CMNH 6090 (left)",
                                                    "CMNH 5768 (left)"),1,0.5))),
            nudge_y=ifelse(residuals(lm(OOL~inferognathal_length,
                                        dunkleosteus_jaws%>%drop_na(inferognathal_length,OOL)
                                        ))<0,-1,1))
Plot of total inferognathal length versus orbit-opercular length in specimens of Dunkleosteus terrelli for which both measurements are known.

Figure 14.6: Plot of total inferognathal length versus orbit-opercular length in specimens of Dunkleosteus terrelli for which both measurements are known.

Based on these results, the length of the biting portion of the inferognathal and the height of the posterior cusp of the inferognathal are the best measurements to estimate total length using the correlation between orbit-opercular length and inferognathal dimensions. These are also the dimensions that are most robust to preservational biases and taphonomic distortion (the total length of the inferognathal is often not preserved, as in CMNH 5936), inferognathal wear (JM4 can be variable due to wear), and can be easily identified across taxa (JM1 and JM2 can be hard to identify in worn jaws and are dependent on characters unique to Dunkleosteus, making comparable regression equations difficult to construct for other arthrodires).

14.2 Allometry of inferognathal dimensions in Dunkleosteus terrelli

data.frame("JM1"=c(summary(JM1_allometry)$r.squared,
                   summary(JM1_allometry)$coefficients[1,],
                   summary(JM1_allometry)$coefficients[2,]),
           "JM2"=c(summary(JM2_allometry)$r.squared,
                   summary(JM2_allometry)$coefficients[1,],
                   summary(JM2_allometry)$coefficients[2,]),
           "JM3"=c(summary(JM3_allometry)$r.squared,
                   summary(JM3_allometry)$coefficients[1,],
                   summary(JM3_allometry)$coefficients[2,]),
           "JM4"=c(summary(JM4_allometry)$r.squared,
                   summary(JM4_allometry)$coefficients[1,],
                   summary(JM4_allometry)$coefficients[2,]),
           "JM5"=c(summary(JM5_allometry)$r.squared,
                   summary(JM5_allometry)$coefficients[1,],
                   summary(JM5_allometry)$coefficients[2,]),
           "Inferognathal Length"=
             c(summary(inferognathal_length_allometry)$r.squared,
               summary(inferognathal_length_allometry)$coefficients[1,],
               summary(inferognathal_length_allometry)$coefficients[2,]))%>%
  t()%>%
  data.frame(row.names=c("JM1","JM2","JM3","JM4","JM5","Inferognathal Length"))%>%
  select(X1,X6,X7,X8,X9,X2,X3,X4,X5)%>% # Moving slope to be before intercept
  kable(digits=3,align="c",
        col.names=c("r2","Estimate","Std. Error","T Value","Pr(>|t|)","Estimate","Std. Error","T Value","Pr(>|t|)"),
        caption="Examination of allometry of inferognathal dimensions in <i>Dunkleosteus terrelli</i> relative to OOL.")%>%
  add_header_above(c(" "=2,"Slope"=4,"Intercept"=4))%>%
  column_spec(c(3,7),bold=T)%>%
  kable_styling()
Table 14.2: Examination of allometry of inferognathal dimensions in Dunkleosteus terrelli relative to OOL.
Slope
Intercept
r2 Estimate Std. Error T Value Pr(>|t|) Estimate Std. Error T Value Pr(>|t|)
JM1 0.916 1.062 0.093 11.467 0 -1.700 0.332 -5.115 0.000
JM2 0.858 1.205 0.142 8.517 0 -2.652 0.508 -5.225 0.000
JM3 0.968 0.702 0.036 19.726 0 2.020 0.081 25.058 0.000
JM4 0.951 1.274 0.083 15.312 0 -2.331 0.299 -7.807 0.000
JM5 0.981 1.294 0.050 25.875 0 -1.814 0.180 -10.083 0.000
Inferognathal Length 0.969 1.230 0.061 20.063 0 -0.848 0.221 -3.843 0.002

The log slope is positive for length variables that span the overall dimensions of the jaw (JM5, inferognathal length), indicating positive allometry of the mouth and jaws relative to OOL.

Because these results are based on the regression between jaw dimensions and OOL, it is possible they could be biased by allometry. Actinopterygians and lampreys show positive ontogenetic allometry of OOL, though sharks and the extinct sarcopterygian Eusthenopteron do not. Because of this it is worth testing these patterns against head length, which is expected to scale closer to isometry in Dunkleosteus terrelli.

data.frame("JM1"=c(summary(lm(log(JM1)~log(head_length),dunkleosteus_jaws))$r.squared,
                   summary(lm(log(JM1)~log(head_length),dunkleosteus_jaws))$coefficients[1,],
                   summary(lm(log(JM1)~log(head_length),dunkleosteus_jaws))$coefficients[2,]),
           "JM2"=c(summary(lm(log(JM2)~log(head_length),dunkleosteus_jaws))$r.squared,
                   summary(lm(log(JM2)~log(head_length),dunkleosteus_jaws))$coefficients[1,],
                   summary(lm(log(JM2)~log(head_length),dunkleosteus_jaws))$coefficients[2,]),
           "JM3"=c(summary(lm(log(JM3)~log(head_length),dunkleosteus_jaws))$r.squared,
                   summary(lm(log(JM3)~log(head_length),dunkleosteus_jaws))$coefficients[1,],
                   summary(lm(log(JM3)~log(head_length),dunkleosteus_jaws))$coefficients[2,]),
           "JM4"=c(summary(lm(log(JM4)~log(head_length),dunkleosteus_jaws))$r.squared,
                   summary(lm(log(JM4)~log(head_length),dunkleosteus_jaws))$coefficients[1,],
                   summary(lm(log(JM4)~log(head_length),dunkleosteus_jaws))$coefficients[2,]),
           "JM5"=c(summary(lm(log(JM5)~log(head_length),dunkleosteus_jaws))$r.squared,
                   summary(lm(log(JM5)~log(head_length),dunkleosteus_jaws))$coefficients[1,],
                   summary(lm(log(JM5)~log(head_length),dunkleosteus_jaws))$coefficients[2,]),
           "Inferognathal Length"=
             c(summary(lm(log(inferognathal_length)~log(head_length),dunkleosteus_jaws))$r.squared,
               summary(lm(log(inferognathal_length)~log(head_length),dunkleosteus_jaws))$coefficients[1,],
               summary(lm(log(inferognathal_length)~log(head_length),dunkleosteus_jaws))$coefficients[2,]))%>%
  t()%>%
  data.frame(row.names=c("JM1","JM2","JM3","JM4","JM5","Inferognathal Length"))%>%
  select(X1,X6,X7,X8,X9,X2,X3,X4,X5)%>% # Moving slope to be before intercept
  kable(digits=3,align="c",
        col.names=c("r2","Estimate","Std. Error","T Value","Pr(>|t|)","Estimate","Std. Error","T Value","Pr(>|t|)"),
        caption="Examination of allometry of inferognathal dimensions in <i>Dunkleosteus terrelli</i> relative to head length.")%>%
  add_header_above(c(" "=2,"Slope"=4,"Intercept"=4))%>%
  column_spec(c(3,7),bold=T)%>%
  kable_styling()
Table 14.3: Examination of allometry of inferognathal dimensions in Dunkleosteus terrelli relative to head length.
Slope
Intercept
r2 Estimate Std. Error T Value Pr(>|t|) Estimate Std. Error T Value Pr(>|t|)
JM1 0.914 1.033 0.096 10.782 0 -1.758 0.361 -4.873 0.000
JM2 0.865 1.209 0.144 8.408 0 -2.883 0.542 -5.322 0.000
JM3 0.971 1.364 0.068 19.915 0 -2.884 0.258 -11.156 0.000
JM4 0.957 1.260 0.080 15.673 0 -2.487 0.303 -8.214 0.000
JM5 0.978 1.276 0.055 23.262 0 -1.959 0.207 -9.460 0.000
Inferognathal Length 0.968 1.215 0.064 19.094 0 -0.995 0.240 -4.142 0.001

Similar to the results with OOL, JM5 and inferognathal length both show positive allometry relative to head length.

14.3 Estimating total length in CMNH 5936

14.3.1 Using JM3

JM3_estimates<-dunkleosteus_jaws%>%
  mutate(OOL=predict(fit.JM3,.),
         clade="Placodermi",shape="fusiform",family="Dunkleosteidae")%>%
  augment(x=fit.OOL,newdata=.,interval="prediction") %>% 
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.OOL)$CF),)%>%
  rename_at(vars(starts_with('.')), funs(paste0('fit1', .)))%>%
    
  augment(x=fit.species_average,newdata=.,interval="prediction") %>% 
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.species_average)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0('fit2', .)))%>%
    
  augment(x=fit.fusiform,newdata=.,interval="prediction") %>% 
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.fusiform)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0('fit3', .)))%>%
    
  augment(x=fit.fusiform_averages,newdata=.,interval="prediction") %>% 
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.fusiform_averages)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0('fit4', .)))%>%
    
  augment(x=fit.shape_clade3,newdata=.,interval="prediction") %>% 
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.shape_clade3)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0('fit5', .)))%>%
    
  augment(x=fit.shape_clade_species_averages,newdata=.,interval="prediction") %>% 
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.shape_clade4)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0('fit6', .)))%>%
  mutate(specimen=dunkleosteus_jaws$specimen,
         fit1.PE=paste0("(",
                    sprintf("%.1f",
                            round(fit1.fitted*(1-(regression.stats(fit.OOL)$adjPE/100)),1)),
                    "–",
                    sprintf("%.1f",
                            round(fit1.fitted*(1+(regression.stats(fit.OOL)$adjPE/100)),1)),
                    ")"),
         fit2.PE=paste0("(",
                    sprintf("%.1f",
                            round(fit2.fitted*(1-(regression.stats(fit.species_average)$adjPE/100)),1)),
                    "–",
                    sprintf("%.1f",
                            round(fit2.fitted*(1+(regression.stats(fit.species_average)$adjPE/100)),1)),
                    ")"),
         fit3.PE=paste0("(",
                    sprintf("%.1f",
                            round(fit3.fitted*(1-(regression.stats(fit.fusiform)$adjPE/100)),1)),
                    "–",
                    sprintf("%.1f",
                            round(fit3.fitted*(1+(regression.stats(fit.fusiform)$adjPE/100)),1)),
                    ")"),
         fit4.PE=paste0("(",
                    sprintf("%.1f",
                            round(fit4.fitted*(1-(regression.stats(fit.fusiform_averages)$adjPE/100)),1)),
                    "–",
                    sprintf("%.1f",
                            round(fit4.fitted*(1+(regression.stats(fit.fusiform_averages)$adjPE/100)),1)),
                    ")"),
         fit5.PE=paste0("(",
                    sprintf("%.1f",
                            round(fit5.fitted*(1-(regression.stats(fit.shape_clade3)$adjPE/100)),1)),
                    "–",
                    sprintf("%.1f",
                            round(fit5.fitted*(1+(regression.stats(fit.shape_clade3)$adjPE/100)),1)),
                    ")"),
         fit6.PE=paste0("(",
                    sprintf("%.1f",
                            round(fit6.fitted*(1-(regression.stats(fit.shape_clade4)$adjPE/100)),1)),
                    "–",
                    sprintf("%.1f",
                            round(fit6.fitted*(1+(regression.stats(fit.shape_clade4)$adjPE/100)),1)),
                    ")"),
         fit1.range=paste0("(",sprintf("%.1f",round(fit1.lower,1)),
                           "–",sprintf("%.1f",round(fit1.upper,1)),")"),
         fit2.range=paste0("(",sprintf("%.1f",round(fit2.lower,1))
                           ,"–",sprintf("%.1f",round(fit2.upper,1)),")"),
         fit3.range=paste0("(",sprintf("%.1f",round(fit3.lower,1)),
                           "–",sprintf("%.1f",round(fit3.upper,1)),")"),
         fit4.range=paste0("(",sprintf("%.1f",round(fit4.lower,1)),
                           "–",sprintf("%.1f",round(fit4.upper,1)),")"),
         fit5.range=paste0("(",sprintf("%.1f",round(fit5.lower,1)),
                           "–",sprintf("%.1f",round(fit5.upper,1)),")"),
         fit6.range=paste0("(",sprintf("%.1f",round(fit6.lower,1)),
                           "–",sprintf("%.1f",round(fit6.upper,1)),")"))%>%
  remove_rownames()%>%
  column_to_rownames("specimen")%>%
  select(fit1.fitted,fit1.PE,fit1.range,
         fit2.fitted,fit2.PE,fit2.range,
         fit3.fitted,fit3.PE,fit3.range,
         fit4.fitted,fit4.PE,fit4.range,
         fit5.fitted,fit5.PE,fit5.range,
         fit6.fitted,fit6.PE,fit6.range)

JM3_estimates%>%
  kable(align="c", digits=1,
        col.names=c("Estimate","+/- %PE","95% C.I.",
                    "Estimate","+/- %PE","95% C.I.",
                    "Estimate","+/- %PE","95% C.I.",
                    "Estimate","+/- %PE","95% C.I.",
                    "Estimate","+/- %PE","95% C.I.",
                    "Estimate","+/- %PE","95% C.I."),
        caption="Length estimates of the largest known specimen of <i>Dunkleosteus terrelli</i> (CMNH 5936) using JM3 (height of the inferognathal at posterior cusp). Estimates calculated by estimating OOL using the regression between JM3 and OOL, and then inserting that value into the respective equation for total length. All measurements in cm.")%>%
  add_header_above(c(" "=1,"All specimens"=3,"Species averages"=3,
                     "All specimens"=3,"Species averages"=3,
                     "All specimens"=3,"Species averages"=3))%>%
  add_header_above(c(" "=1,"All taxa"=6,"Fusiform fishes"=6,"With variable slopes for Chondrichthyes"=6))%>%
  column_spec(c(2,5,8,11,14,17),bold=T)%>%
  kable_styling()%>%
  scroll_box(width = "100%")
Table 14.4: Length estimates of the largest known specimen of Dunkleosteus terrelli (CMNH 5936) using JM3 (height of the inferognathal at posterior cusp). Estimates calculated by estimating OOL using the regression between JM3 and OOL, and then inserting that value into the respective equation for total length. All measurements in cm.
All taxa
Fusiform fishes
With variable slopes for Chondrichthyes
All specimens
Species averages
All specimens
Species averages
All specimens
Species averages
Estimate +/- %PE 95% C.I. Estimate +/- %PE 95% C.I. Estimate +/- %PE 95% C.I. Estimate +/- %PE 95% C.I. Estimate +/- %PE 95% C.I. Estimate +/- %PE 95% C.I.
CMNH 5936 409.4 (337.6–481.3) (263.4–636.5) 392.7 (322.6–462.8) (248.0–622.0) 369.8 (325.9–413.7) (273.1–500.7) 362.7 (321.9–403.6) (270.4–486.7) 395.4 (346.4–444.5) (284.4–549.8) 339.4 (293.7–385.1) (179.0–643.6)
CMNH 5768 (right) 338.8 (279.3–398.2) (217.9–526.6) 325.8 (267.7–384.0) (205.8–515.9) 307.4 (270.9–344.0) (227.1–416.3) 302.0 (268.0–336.0) (225.2–405.1) 327.4 (286.8–368.0) (235.5–455.1) 283.5 (245.3–321.7) (154.8–519.4)
CMNH 5768 (left) 360.4 (297.1–423.6) (231.8–560.2) 346.3 (284.5–408.1) (218.7–548.4) 326.5 (287.7–365.3) (241.2–442.1) 320.6 (284.5–356.7) (239.0–430.1) 348.2 (305.0–391.4) (250.5–484.1) 300.7 (260.2–341.2) (162.3–557.0)
CMNH 8131 314.0 (258.9–369.2) (202.0–488.2) 302.4 (248.4–356.4) (191.0–478.7) 285.5 (251.6–319.5) (210.9–386.6) 280.7 (249.1–312.3) (209.3–376.5) 303.6 (265.9–341.2) (218.4–422.0) 263.8 (228.3–299.3) (146.0–476.8)
CMNH 7054 (left) 305.4 (251.8–359.0) (196.5–474.8) 294.2 (241.7–346.7) (185.8–465.8) 277.9 (244.9–310.9) (205.3–376.3) 273.3 (242.5–304.0) (203.7–366.5) 295.3 (258.6–331.9) (212.4–410.4) 256.9 (222.3–291.5) (142.9–462.1)
CMNH 7054 (right) 313.1 (258.2–368.1) (201.4–486.7) 301.5 (247.7–355.3) (190.4–477.4) 284.7 (250.9–318.6) (210.3–385.5) 279.9 (248.4–311.4) (208.7–375.4) 302.7 (265.1–340.2) (217.7–420.8) 263.1 (227.6–298.5) (145.6–475.3)
CMNH 6090 (left) 284.4 (234.5–334.3) (183.0–442.0) 274.2 (225.3–323.1) (173.2–434.1) 259.2 (228.4–290.0) (191.4–350.9) 255.0 (226.3–283.7) (190.2–342.0) 275.0 (240.9–309.0) (197.8–382.2) 240.1 (207.7–272.4) (135.1–426.5)
CMNH 6090 (right) 295.7 (243.8–347.6) (190.3–459.7) 285.0 (234.1–335.9) (180.0–451.2) 269.3 (237.3–301.3) (198.9–364.6) 264.9 (235.0–294.7) (197.5–355.2) 285.9 (250.4–321.4) (205.7–397.4) 249.2 (215.6–282.7) (139.3–445.6)
CMNH 5740 269.5 (222.2–316.8) (173.4–418.9) 260.1 (213.7–306.5) (164.3–411.7) 246.0 (216.8–275.2) (181.7–333.1) 242.1 (214.9–269.4) (180.6–324.7) 260.6 (228.3–293.0) (187.5–362.3) 228.2 (197.4–258.9) (129.6–401.6)
CMNH OC 1421 249.9 (206.1–293.8) (160.8–388.5) 241.4 (198.3–284.5) (152.5–382.1) 228.5 (201.4–255.7) (168.8–309.4) 225.1 (199.7–250.4) (167.9–301.8) 241.7 (211.8–271.7) (173.9–336.0) 212.4 (183.7–241.0) (122.2–369.0)
CMNH 5007 200.8 (165.5–236.0) (129.2–312.1) 194.5 (159.8–229.3) (122.9–307.9) 184.6 (162.7–206.5) (136.4–249.9) 182.1 (161.6–202.6) (135.9–244.2) 194.3 (170.2–218.4) (139.8–270.1) 172.5 (149.2–195.7) (102.8–289.3)
CMNH 7424 196.6 (162.1–231.1) (126.5–305.5) 190.5 (156.5–224.5) (120.4–301.5) 180.8 (159.3–202.3) (133.6–244.8) 178.4 (158.3–198.5) (133.1–239.2) 190.3 (166.7–213.8) (136.9–264.4) 169.0 (146.3–191.8) (101.1–282.6)
CMNH 6194 (left) 163.6 (134.9–192.3) (105.3–254.3) 159.0 (130.6–187.4) (100.5–251.6) 151.2 (133.3–169.2) (111.7–204.7) 149.4 (132.6–166.3) (111.5–200.3) 158.5 (138.8–178.1) (114.0–220.2) 142.0 (122.9–161.1) (87.3–231.0)
CMNH 6194 (right) 169.7 (139.9–199.4) (109.2–263.7) 164.8 (135.4–194.2) (104.1–260.7) 156.6 (138.0–175.3) (115.7–212.1) 154.8 (137.3–172.2) (115.4–207.5) 164.3 (143.9–184.7) (118.2–228.3) 147.0 (127.2–166.8) (89.9–240.3)
CMNH 50322 171.0 (141.0–201.0) (110.0–265.8) 166.1 (136.4–195.7) (105.0–262.8) 157.9 (139.1–176.6) (116.6–213.7) 156.0 (138.4–173.5) (116.3–209.1) 165.6 (145.1–186.1) (119.1–230.1) 148.1 (128.1–168.0) (90.5–242.4)
CMNH 8982 153.1 (126.2–179.9) (98.5–237.9) 148.9 (122.3–175.5) (94.1–235.5) 141.7 (124.8–158.5) (104.7–191.8) 140.1 (124.3–155.9) (104.5–187.8) 148.3 (129.9–166.6) (106.7–206.0) 133.3 (115.3–151.2) (82.7–214.8)

14.3.2 Using JM5

JM5_estimates<-dunkleosteus_jaws%>%
  mutate(OOL=predict(fit.JM5,.),
         clade="Placodermi",shape="fusiform",family="Dunkleosteidae")%>%
  augment(x=fit.OOL,newdata=.,interval="prediction") %>% 
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.OOL)$CF),)%>%
  rename_at(vars(starts_with('.')), funs(paste0('fit1', .)))%>%
    
  augment(x=fit.species_average,newdata=.,interval="prediction") %>% 
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.species_average)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0('fit2', .)))%>%
    
  augment(x=fit.fusiform,newdata=.,interval="prediction") %>% 
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.fusiform)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0('fit3', .)))%>%
    
  augment(x=fit.fusiform_averages,newdata=.,interval="prediction") %>% 
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.fusiform_averages)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0('fit4', .)))%>%
    
  augment(x=fit.shape_clade3,newdata=.,interval="prediction") %>% 
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.shape_clade3)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0('fit5', .)))%>%
    
  augment(x=fit.shape_clade_species_averages,newdata=.,interval="prediction") %>% 
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.shape_clade4)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0('fit6', .)))%>%
  mutate(specimen=dunkleosteus_jaws$specimen,
         fit1.PE=paste0("(",
                    sprintf("%.1f",
                            round(fit1.fitted*(1-(regression.stats(fit.OOL)$adjPE/100)),1)),
                    "–",
                    sprintf("%.1f",
                            round(fit1.fitted*(1+(regression.stats(fit.OOL)$adjPE/100)),1)),
                    ")"),
         fit2.PE=paste0("(",
                    sprintf("%.1f",
                        round(fit2.fitted*(1-(regression.stats(fit.species_average)$adjPE/100)),1)),
                    "–",
                    sprintf("%.1f",
                        round(fit2.fitted*(1+(regression.stats(fit.species_average)$adjPE/100)),1)),
                    ")"),
         fit3.PE=paste0("(",
                    sprintf("%.1f",
                            round(fit3.fitted*(1-(regression.stats(fit.fusiform)$adjPE/100)),1)),
                    "–",
                    sprintf("%.1f",
                            round(fit3.fitted*(1+(regression.stats(fit.fusiform)$adjPE/100)),1)),
                    ")"),
         fit4.PE=paste0("(",
                    sprintf("%.1f",
                      round(fit4.fitted*(1-(regression.stats(fit.fusiform_averages)$adjPE/100)),1)),
                    "–",
                    sprintf("%.1f",
                      round(fit4.fitted*(1+(regression.stats(fit.fusiform_averages)$adjPE/100)),1)),
                    ")"),
         fit5.PE=paste0("(",
                    sprintf("%.1f",
                            round(fit5.fitted*(1-(regression.stats(fit.shape_clade3)$adjPE/100)),1)),
                    "–",
                    sprintf("%.1f",
                            round(fit5.fitted*(1+(regression.stats(fit.shape_clade3)$adjPE/100)),1)),
                    ")"),
         fit6.PE=paste0("(",
                    sprintf("%.1f",
                            round(fit6.fitted*(1-(regression.stats(fit.shape_clade4)$adjPE/100)),1)),
                    "–",
                    sprintf("%.1f",
                            round(fit6.fitted*(1+(regression.stats(fit.shape_clade4)$adjPE/100)),1)),
                    ")"),
         fit1.range=paste0("(",sprintf("%.1f",round(fit1.lower,1)),"–",
                           sprintf("%.1f",round(fit1.upper,1)),")"),
         fit2.range=paste0("(",sprintf("%.1f",round(fit2.lower,1)),"–",
                           sprintf("%.1f",round(fit2.upper,1)),")"),
         fit3.range=paste0("(",sprintf("%.1f",round(fit3.lower,1)),"–",
                           sprintf("%.1f",round(fit3.upper,1)),")"),
         fit4.range=paste0("(",sprintf("%.1f",round(fit4.lower,1)),"–",
                           sprintf("%.1f",round(fit4.upper,1)),")"),
         fit5.range=paste0("(",sprintf("%.1f",round(fit5.lower,1)),"–",
                           sprintf("%.1f",round(fit5.upper,1)),")"),
         fit6.range=paste0("(",sprintf("%.1f",round(fit6.lower,1)),"–",
                           sprintf("%.1f",round(fit6.upper,1)),")"))%>%
  remove_rownames()%>%
  column_to_rownames("specimen")%>%
  select(fit1.fitted,fit1.PE,fit1.range,
         fit2.fitted,fit2.PE,fit2.range,
         fit3.fitted,fit3.PE,fit3.range,
         fit4.fitted,fit4.PE,fit4.range,
         fit5.fitted,fit5.PE,fit5.range,
         fit6.fitted,fit6.PE,fit6.range)

JM5_estimates%>%
  kable(align="c", digits=1,
        col.names=c("Estimate","+/- %PE","95% C.I.",
                    "Estimate","+/- %PE","95% C.I.",
                    "Estimate","+/- %PE","95% C.I.",
                    "Estimate","+/- %PE","95% C.I.",
                    "Estimate","+/- %PE","95% C.I.",
                    "Estimate","+/- %PE","95% C.I."),
        caption="Length estimates of the largest known specimen of <i>Dunkleosteus terrelli</i> (CMNH 5936) using JM5 (length of the biting portion of the inferognathal). Estimates calculated by estimating OOL using the regression between JM5 and OOL, and then inserting that value into the respective equation for total length. All measurements in cm.")%>%
  add_header_above(c(" "=1,"All specimens"=3,"Species averages"=3,
                     "All specimens"=3,"Species averages"=3,
                     "All specimens"=3,"Species averages"=3))%>%
  add_header_above(c(" "=1,"All taxa"=6,"Fusiform fishes"=6,"With variable slopes for Chondrichthyes"=6))%>%
  column_spec(c(2,5,8,11,14,17),bold=T)%>%
  kable_styling()%>%
  scroll_box(width = "100%")
Table 14.5: Length estimates of the largest known specimen of Dunkleosteus terrelli (CMNH 5936) using JM5 (length of the biting portion of the inferognathal). Estimates calculated by estimating OOL using the regression between JM5 and OOL, and then inserting that value into the respective equation for total length. All measurements in cm.
All taxa
Fusiform fishes
With variable slopes for Chondrichthyes
All specimens
Species averages
All specimens
Species averages
All specimens
Species averages
Estimate +/- %PE 95% C.I. Estimate +/- %PE 95% C.I. Estimate +/- %PE 95% C.I. Estimate +/- %PE 95% C.I. Estimate +/- %PE 95% C.I. Estimate +/- %PE 95% C.I.
CMNH 5936 423.5 (349.2–497.8) (272.4–658.4) 406.0 (333.6–478.5) (256.4–643.1) 382.2 (336.8–427.6) (282.3–517.5) 374.8 (332.6–417.0) (279.4–502.9) 409.0 (358.3–459.7) (294.2–568.6) 350.5 (303.3–397.7) (183.7–668.8)
CMNH 5768 (right) 360.7 (297.4–424.0) (232.1–560.7) 346.6 (284.8–408.5) (218.9–548.9) 326.8 (288.0–365.7) (241.4–442.6) 321.0 (284.8–357.1) (239.3–430.5) 348.5 (305.3–391.8) (250.7–484.5) 300.9 (260.4–341.5) (162.4–557.6)
CMNH 5768 (left) 340.6 (280.8–400.4) (219.1–529.5) 327.6 (269.1–386.1) (206.9–518.7) 309.1 (272.4–345.8) (228.3–418.5) 303.6 (269.5–337.8) (226.4–407.3) 329.2 (288.3–370.0) (236.8–457.6) 285.0 (246.6–323.4) (155.4–522.6)
CMNH 8131 319.1 (263.1–375.1) (205.3–496.0) 307.2 (252.3–362.0) (194.0–486.3) 290.0 (255.6–324.5) (214.2–392.7) 285.1 (253.0–317.2) (212.5–382.3) 308.4 (270.2–346.7) (221.9–428.7) 267.8 (231.8–303.9) (147.8–485.5)
CMNH 7054 (left) 313.6 (258.6–368.6) (201.7–487.5) 302.0 (248.1–355.8) (190.7–478.1) 285.1 (251.3–319.0) (210.6–386.1) 280.3 (248.7–311.9) (209.0–376.0) 303.1 (265.5–340.7) (218.1–421.4) 263.5 (228.0–298.9) (145.8–476.1)
CMNH 7054 (right) 303.6 (250.4–356.9) (195.3–472.0) 292.5 (240.3–344.7) (184.8–463.1) 276.3 (243.5–309.1) (204.1–374.1) 271.7 (241.1–302.3) (202.6–364.4) 293.5 (257.1–329.9) (211.2–408.0) 255.5 (221.1–289.9) (142.2–459.1)
CMNH 6090 (left) 277.5 (228.8–326.2) (178.5–431.3) 267.6 (219.9–315.4) (169.1–423.7) 253.1 (223.0–283.1) (186.9–342.6) 249.0 (221.0–277.1) (185.7–334.0) 268.3 (235.0–301.6) (193.0–373.0) 234.5 (202.9–266.1) (132.6–414.9)
CMNH 6090 (right) 285.0 (235.0–335.1) (183.4–443.1) 274.8 (225.8–323.9) (173.6–435.1) 259.8 (228.9–290.7) (191.9–351.7) 255.6 (226.8–284.4) (190.6–342.8) 275.6 (241.4–309.8) (198.3–383.1) 240.6 (208.2–273.0) (135.4–427.6)
CMNH 5740 277.4 (228.7–326.0) (178.4–431.1) 267.5 (219.8–315.3) (169.0–423.5) 253.0 (222.9–283.0) (186.8–342.5) 248.9 (220.9–277.0) (185.6–333.8) 268.2 (234.9–301.5) (192.9–372.8) 234.4 (202.9–266.0) (132.5–414.7)
CMNH OC 1421 255.0 (210.3–299.8) (164.1–396.4) 246.3 (202.3–290.3) (155.6–389.9) 233.1 (205.4–260.8) (172.2–315.6) 229.5 (203.7–255.4) (171.2–307.8) 246.7 (216.1–277.3) (177.5–342.9) 216.5 (187.3–245.6) (124.1–377.5)
CMNH 5007 201.5 (166.1–236.8) (129.6–313.2) 195.2 (160.4–230.1) (123.4–308.9) 185.2 (163.2–207.2) (136.8–250.8) 182.8 (162.2–203.3) (136.3–245.0) 195.0 (170.8–219.2) (140.3–271.0) 173.0 (149.7–196.4) (103.1–290.4)
CMNH 7424 188.9 (155.8–222.1) (121.6–293.6) 183.2 (150.5–215.9) (115.8–289.9) 174.0 (153.3–194.6) (128.5–235.5) 171.7 (152.4–191.1) (128.1–230.2) 182.9 (160.2–205.6) (131.6–254.2) 162.8 (140.9–184.7) (98.0–270.5)
CMNH 6194 (left) 176.2 (145.3–207.1) (113.4–273.8) 171.0 (140.5–201.6) (108.1–270.6) 162.5 (143.2–181.8) (120.0–220.0) 160.5 (142.4–178.6) (119.7–215.2) 170.6 (149.4–191.7) (122.7–237.1) 152.3 (131.8–172.9) (92.6–250.5)
CMNH 6194 (right) 175.4 (144.6–206.2) (112.9–272.6) 170.3 (139.9–200.7) (107.6–269.5) 161.8 (142.6–181.0) (119.5–219.1) 159.8 (141.8–177.8) (119.2–214.3) 169.8 (148.8–190.9) (122.2–236.0) 151.7 (131.3–172.1) (92.3–249.3)
CMNH 50322 160.0 (131.9–188.0) (102.9–248.6) 155.5 (127.8–183.3) (98.3–246.0) 147.9 (130.3–165.5) (109.3–200.2) 146.2 (129.7–162.7) (109.1–196.0) 154.9 (135.7–174.1) (111.5–215.3) 139.0 (120.3–157.7) (85.7–225.4)
CMNH 8982 151.4 (124.8–178.0) (97.4–235.3) 147.3 (121.0–173.6) (93.1–233.1) 140.2 (123.5–156.9) (103.6–189.8) 138.6 (123.0–154.3) (103.4–185.8) 146.7 (128.5–164.9) (105.5–203.8) 131.9 (114.1–149.7) (82.0–212.3)
cbind(
  JM3_estimates%>%
  slice(1)%>%
  mutate(across(where(is.numeric), ~as.character(sprintf("%.1f", round(.,1))))) %>%
  rename_at(vars(starts_with('fit1.')), funs(gsub('fit1.','JM3_All specimens_Individual Data_', .)))%>%
  rename_at(vars(starts_with('fit2.')), funs(gsub('fit2.','JM3_All specimens_Species Averages_', .)))%>%
  rename_at(vars(starts_with('fit3.')), funs(gsub('fit3.','JM3_Fusiform fishes only_Individual Data_', .)))%>%
  rename_at(vars(starts_with('fit4.')), funs(gsub('fit4.','JM3_Fusiform fishes only_Species Averages_', .)))%>%
  rename_at(vars(starts_with('fit5.')), funs(gsub('fit5.','JM3_Variable slope for chondrichthyans_Individual Data_', .)))%>%
  rename_at(vars(starts_with('fit6.')), funs(gsub('fit6.','JM3_Variable slope for chondrichthyans_Species Averages_', .))),
  JM5_estimates%>%
    slice(1)%>%
    mutate(across(where(is.numeric), ~as.character(sprintf("%.1f", round(.,1))))) %>%
    rename_at(vars(starts_with('fit1.')), funs(gsub('fit1.','JM5_All specimens_Individual Data_', .)))%>%
    rename_at(vars(starts_with('fit2.')), funs(gsub('fit2.','JM5_All specimens_Species Averages_', .)))%>%
    rename_at(vars(starts_with('fit3.')), funs(gsub('fit3.','JM5_Fusiform fishes only_Individual Data_', .)))%>%
    rename_at(vars(starts_with('fit4.')), funs(gsub('fit4.','JM5_Fusiform fishes only_Species Averages_', .)))%>%
    rename_at(vars(starts_with('fit5.')), funs(gsub('fit5.','JM5_Variable slope for chondrichthyans_Individual Data_', .)))%>%
    rename_at(vars(starts_with('fit6.')), funs(gsub('fit6.','JM5_Variable slope for chondrichthyans_Species Averages_', .)))
)%>%
  pivot_longer(data=.,
               cols=everything(),
               names_to = c("measurement","model","type","data_value"),
               names_sep = "_")%>%
  mutate(total=paste(measurement,model,type,sep="_"),
         name=rep(c("Est.","+/- PE","95% P.I."),12))%>%
  select(total,value,name)%>%
  pivot_wider(id_cols="total",
              names_from="name")%>%
  separate(col=total,
           into=c("Measurement","Model","Type"),
           sep="_")%>%
  write.xlsx(.,"Devonian Fish Tale Table 4 (CMNH 5936).xlsx")
cbind(
  JM3_estimates %>% slice(1) %>% rename_with(~str_c("JM3_",.),.cols=everything()),
  JM5_estimates %>% slice(1) %>% rename_with(~str_c("JM5_",.),.cols=everything()))%>%
  select(ends_with(".fitted"))%>%
  gather()%>%
  summarise(mean=mean(value),median=median(value))%>%
  kable(digits=1,
        col.names=c("Mean","Median"),
        caption="Mean and median length estimates for the largest known specimen of <i>Dunkleosteus terrelli</i> (CMNH 5936)",
        table.attr = "style='width:70%;'")%>%
  kable_styling()
Table 14.6: Mean and median length estimates for the largest known specimen of Dunkleosteus terrelli (CMNH 5936)
Mean Median
384.6 387.5

14.4 Predicted lengths for CMNH 5936 using other jaw measurements

CMNH_5936_estimates<-dunkleosteus_jaws %>%
  filter(specimen=="CMNH 5936") %>%
  select(JM1:JM5)%>%
  mutate(shape="fusiform",clade="Placodermi",family="Dunkleosteidae",
         inferognathal_length=predict(lm(inferognathal_length~JM5,dunkleosteus_jaws),newdata=.))%>%
  augment(lm(OOL~JM1,dunkleosteus_jaws),newdata=.) %>%
  rename(JM1.fitted=.fitted)%>%
  augment(lm(OOL~JM2,dunkleosteus_jaws),newdata=.) %>%
  rename(JM2.fitted=.fitted)%>%
  augment(fit.JM3,newdata=.) %>%
  rename(JM3.fitted=.fitted)%>%
  augment(lm(OOL~JM4,dunkleosteus_jaws),newdata=.) %>%
  rename(JM4.fitted=.fitted)%>%
  augment(fit.JM5,newdata=.) %>%
  rename(JM5.fitted=.fitted)%>%
  augment(lm(OOL~inferognathal_length,dunkleosteus_jaws),newdata=.) %>%
  rename(inferognathal_length.fitted=.fitted)

CMNH_5936_estimates<-CMNH_5936_estimates %>%
  mutate(length_JM1=exp(predict(fit.shape_clade3,newdata=CMNH_5936_estimates%>%rename(OOL=JM1.fitted)))*regression.stats(fit.shape_clade3)$CF,
         length_JM2=exp(predict(fit.shape_clade3,newdata=CMNH_5936_estimates%>%rename(OOL=JM2.fitted)))*regression.stats(fit.shape_clade3)$CF,
         length_JM3=exp(predict(fit.shape_clade3,newdata=CMNH_5936_estimates%>%rename(OOL=JM3.fitted)))*regression.stats(fit.shape_clade3)$CF,
         length_JM4=exp(predict(fit.shape_clade3,newdata=CMNH_5936_estimates%>%rename(OOL=JM4.fitted)))*regression.stats(fit.shape_clade3)$CF,
         length_JM5=exp(predict(fit.shape_clade3,newdata=CMNH_5936_estimates%>%rename(OOL=JM5.fitted)))*regression.stats(fit.shape_clade3)$CF,
         length_inf=exp(predict(fit.shape_clade3,newdata=CMNH_5936_estimates%>%rename(OOL=inferognathal_length.fitted)))*regression.stats(fit.shape_clade3)$CF
         )

cbind(CMNH_5936_estimates %>% select(JM1:JM5,inferognathal_length) %>% rename ("Inferognathal Length"=inferognathal_length) %>%t(),
  CMNH_5936_estimates %>% select(JM1.fitted:inferognathal_length.fitted) %>% t(),
  CMNH_5936_estimates %>% select(length_JM1:length_inf) %>% t()) %>%
  kable(col.names=c("Inferognathal Measurement","Estimated OOL","Estimated Total Length"),digits=1,
        align="c",
        caption="Estimated lengths for CMNH 5936 using all jaw measurements considered. Inferognathal length was estimated based on allometric relationship with JM5 in sample of <i>Dunkleosteus terrelli</i>, as the oral region of the inferognathal is consistently around 50% of total inferognathal length in <i>Dunkleosteus</i>. Length estimated from OOL using the equation with shape and a separate allometric line for sharks. All measurements in cm.") %>%
  kable_styling()
Table 14.7: Estimated lengths for CMNH 5936 using all jaw measurements considered. Inferognathal length was estimated based on allometric relationship with JM5 in sample of Dunkleosteus terrelli, as the oral region of the inferognathal is consistently around 50% of total inferognathal length in Dunkleosteus. Length estimated from OOL using the equation with shape and a separate allometric line for sharks. All measurements in cm.
Inferognathal Measurement Estimated OOL Estimated Total Length
JM1 12.8 52.4 339.8
JM2 10.8 60.2 390.0
JM3 18.6 61.0 395.4
JM4 20.4 67.0 433.9
JM5 33.9 63.1 409.0
Inferognathal Length 68.4 62.9 407.4

Based on this none of the inferognathal measurements appear to produce sizes of 5+ m for the largest individuals of Dunkleosteus terrelli, with most producing estimates around 3.9-4.1 m.

15 Comparing support statistics across models

equations<-sapply(c("fit.OOL","fit.no_extreme_shapes","fit.with_shape","fit.fusiform",
                    "fit.relativedepth4","with_snout_length","fit.minus_snout_length1",
                    "fit.pelagic","fit.no_acanthopterygii2","fit.sharks",
                    "fit.shape_clade","fit.shape_clade3",
         "fit.species_average","fit.species_average2","fit.with_shape_averages",
         "fit.fusiform_averages","fit.relativedepth_averages","fit.minus_snout_length2",
         "fit.pelagic_averages","fit.no_acanthopterygii2_averages","fit.sharks_averages",
         "fit.shape_clade_species_averages"), function(x)regression.stats(get(x)))%>%
  t()%>%as.data.frame()%>%
  rownames_to_column()%>%
  mutate(row=1:nrow(.))
equations%>%
  mutate(model = ifelse(row <
                          (equations%>%filter(rowname=="fit.species_average")%>%pull(row)),
                         "individual values","species averages"))%>%
  select(rowname,model,everything())%>%
  select(-row)%>%
  kable()%>%
  kable_styling()%>%
  scroll_box(width = "100%")
rowname model N df r2 adjr2 AIC BIC logLik PE QMLE smear RE CF adjPE SEE
fit.OOL individual values 3169 3167 0.9473 0.9473 -463 -445 234 17.83 1.026 1.027 1.005 1.019 17.55 25.21
fit.no_extreme_shapes individual values 2660 2658 0.962 0.962 -1164 -1147 585 15.38 1.019 1.02 0.993 1.011 15.26 21.44
fit.with_shape individual values 3398 3389 0.9741 0.9741 -2846 -2785 1433 12.1 1.013 1.013 1.003 1.009 12.03 17.23
fit.fusiform individual values 1741 1739 0.9804 0.9804 -1562 -1545 784 11.98 1.012 1.012 1 1.008 11.88 16.69
fit.relativedepth4 individual values 2845 2842 0.95 0.95 -761 -737 384 16.26 1.023 1.024 1.023 1.023 16.17 23.56
with_snout_length individual values 3169 3166 0.9614 0.9614 -1451 -1426 729 14.82 1.019 1.02 1.016 1.018 14.62 21.21
fit.minus_snout_length1 individual values 3169 3165 0.9587 0.9586 -1260 -1230 635 15.55 1.02 1.021 0.999 1.013 15.41 21.92
fit.pelagic individual values 638 636 0.9531 0.9531 -256 -242 131 16.65 1.02 1.02 0.989 1.009 16.56 21.83
fit.no_acanthopterygii2 individual values 2394 2392 0.9602 0.9602 -687 -670 347 16.47 1.022 1.022 1.007 1.017 16.26 23.3
fit.sharks individual values 540 538 0.9619 0.9618 -544 -531 275 11.57 1.011 1.011 1.014 1.012 11.51 15.69
fit.shape_clade individual values 3169 3152 0.9726 0.9725 -2505 -2396 1271 11.84 1.013 1.014 1.014 1.014 11.73 17.65
fit.shape_clade3 individual values 3169 3158 0.9707 0.9707 -2310 -2237 1167 12.45 1.014 1.015 1.016 1.015 12.4 18.27
fit.species_average species averages 967 965 0.9349 0.9349 -66 -51 36 18.01 1.028 1.031 1.016 1.025 17.85 26.29
fit.species_average2 species averages 777 775 0.954 0.954 -266 -252 136 15.45 1.021 1.024 1.003 1.016 15.47 22.55
fit.with_shape_averages species averages 958 949 0.9663 0.966 -689 -640 354 12.69 1.014 1.014 1.004 1.011 12.57 18.29
fit.fusiform_averages species averages 600 598 0.9773 0.9773 -579 -566 293 11.33 1.011 1.011 0.999 1.007 11.26 16.05
fit.relativedepth_averages species averages 914 911 0.9353 0.9351 -86 -67 47 17.76 1.027 1.03 1.016 1.024 17.59 25.89
fit.minus_snout_length2 species averages 967 963 0.9465 0.9463 -259 -235 134 15.93 1.023 1.025 1.011 1.019 15.96 23.49
fit.pelagic_averages species averages 126 124 0.9546 0.9543 -51 -43 29 15.98 1.019 1.019 0.993 1.01 15.84 21.45
fit.no_acanthopterygii2_averages species averages 392 390 0.9773 0.9772 -341 -329 174 12.33 1.012 1.013 0.988 1.004 12.32 16.86
fit.sharks_averages species averages 171 169 0.9568 0.9565 -191 -181 98 10.54 1.009 1.009 1.005 1.008 10.55 14.67
fit.shape_clade_species_averages species averages 967 950 0.9598 0.9592 -502 -415 269 13.36 1.017 1.018 1.015 1.017 13.23 20.3
# Writing model statistics for major models in a single, easily readable table, i.e., Table 1 of the manuscript
(model_stats<-rbind(
  fit.OOL%$%
    cbind(model="All species",
          equation=paste0("Ln(TL) = ",round(.$coefficients[2],4),
                          " * Ln(OOL) ",ifelse(.$coefficients[1]>1,"+","-"),
                          " ",abs(round(.$coefficients[1],4))),
          regression.stats(.)),
  fit.no_extreme_shapes%$%
    cbind(model="Fusiform and elongate taxa",
          equation=paste0("Ln(TL) = ",round(.$coefficients[2],4),
                          " * Ln(OOL) ",ifelse(.$coefficients[1]>1,"+","-"),
                          " ",abs(round(.$coefficients[1],4))),
          regression.stats(.)),
  fit.with_shape%$%
    cbind(model="With shape as covariate",
          equation=paste0("See Supplementary Information"),
          regression.stats(.)),
  fit.fusiform%$%
    cbind(model="Fusiform species only",
          equation=paste0("Ln(TL) = ",round(.$coefficients[2],4),
                          " * Ln(OOL) ",ifelse(.$coefficients[1]>1,"+","-"),
                          " ",abs(round(.$coefficients[1],4))),
          regression.stats(.)),
  fit.relativedepth4%$%
    cbind(model="Including body depth as covariate",
          equation=paste0("See Supplementary Information"),
          regression.stats(.)),
  with_snout_length%$%
    cbind(model="Including snout length as covariate",
          equation=paste0("Ln(TL) = ",round(.$coefficients[3],4),
                          " * Ln(OOL) ",ifelse(.$coefficients[2]>1,"+","-"),
                          " ",abs(round(.$coefficients[2],4))," * Ln(SNL) ",
                          ifelse(.$coefficients[1]>1,"+","-"),
                          " ",abs(round(.$coefficients[1],4))),
          regression.stats(.)),
  fit.pelagic%$%
    cbind(model="Pelagic species only",
          equation=paste0("Ln(TL) = ",round(.$coefficients[2],4),
                          " * Ln(OOL) ",ifelse(.$coefficients[1]>1,"+","-"),
                          " ",abs(round(.$coefficients[1],4))),
          regression.stats(.)),
  fit.no_acanthopterygii2%$%
    cbind(model="Excluding Acanthopterygii, fusiform and elongate taxa only",
          equation=paste0("Ln(TL) = ",round(.$coefficients[2],4),
                          " * Ln(OOL) ",ifelse(.$coefficients[1]>1,"+","-"),
                          " ",abs(round(.$coefficients[1],4))),
          regression.stats(.)),
  fit.sharks%$%
    cbind(model="Sharks only",
        equation=paste0("Ln(TL) = ",round(.$coefficients[2],4),
                        " * Ln(OOL) ",ifelse(.$coefficients[1]>1,"+","-"),
                        " ",abs(round(.$coefficients[1],4))),
        regression.stats(.)),
  fit.shape_clade3%$%
    cbind(model="With shape, allowing variable slope for Chondrichthyes",
          equation=paste0("See Supplementary Information"),
          regression.stats(.)),
  lm(log(total_length)~log(head_length),data_final)%$%
    cbind(model="Head length",
          equation=paste0("Ln(TL) = ",round(.$coefficients[2],4),
                          " * Ln(HDL) ",ifelse(.$coefficients[1]>1,"+","-"),
                          " ",abs(round(.$coefficients[1],4))),
          regression.stats(.))
  )%>%
  rownames_to_column()%>%
  select(model,N,equation,adjr2,AIC,BIC,PE,CF,adjPE,SEE)%>%
  rename(Equation=equation,
         "%PE"=PE,
         "r2adj"=adjr2,
         "%PEcf"=adjPE,
         "%SEE"=SEE)%>%
  column_to_rownames("model")
  )%>%
  write.csv("Devonian Fish Tale Table 1 (Model Statistics).csv")

Overall, the models that produce the best support statistics (AIC, %PEcf) are either the model considering only fusiform fishes (fit.fusiform), the model that includes a crude accounting for shape by including shape as a categorical variable (fit.with_shape), or the model that includes shape and allows slope to differentiate between chondrichthyans and all other fishes due to the largest chondricthyans primarily being short-bodied lamnids and the smallest being elongate-bodied carpet sharks (fit.shape_clade3).

16 Predicting body mass of Dunkleosteus and other arthrodires

16.1 Estimating precaudal and fork length in Dunkleosteus

16.1.1 Estimating fork length using total length of all observations

total_length_to_fork_length<-data_final%>%
  filter(length_as=="total length")%>%
  filter(!(total_length==fork_length))%>%
  filter(!is.na(fork_length))%>%
  lm(log(fork_length)~log(total_length)+(clade=="Placodermi"|clade=="Chondrichthyes")+habitat,.)
summary(total_length_to_fork_length)
## 
## Call:
## lm(formula = log(fork_length) ~ log(total_length) + (clade == 
##     "Placodermi" | clade == "Chondrichthyes") + habitat, data = .)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -2.39328 -0.02269  0.00416  0.03057  0.23894 
## 
## Coefficients:
##                                                        Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                                           -0.092653   0.008881 -10.433  < 2e-16 ***
## log(total_length)                                      1.001728   0.002531 395.773  < 2e-16 ***
## clade == "Placodermi" | clade == "Chondrichthyes"TRUE -0.084416   0.005716 -14.769  < 2e-16 ***
## habitatbenthic                                         0.036664   0.013179   2.782 0.005462 ** 
## habitatneritic                                        -0.017093   0.005080  -3.365 0.000783 ***
## habitatpelagic                                        -0.018487   0.005641  -3.277 0.001069 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.07903 on 1694 degrees of freedom
## Multiple R-squared:  0.994,  Adjusted R-squared:  0.994 
## F-statistic: 5.609e+04 on 5 and 1694 DF,  p-value: < 2.2e-16

16.1.2 Estimating precaudal length using total length of all observations

data_final%>%
  filter(length_as=="total length")%>%
  mutate(clade=relevel(factor(clade),"Placodermi"))%>%
  filter(!is.na(precaudal_length))%$%
  lm(log(precaudal_length)~log(total_length)+clade+habitat,.)%>%
  summary()
## 
## Call:
## lm(formula = log(precaudal_length) ~ log(total_length) + clade + 
##     habitat, data = .)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.71852 -0.02860  0.00179  0.03430  0.20519 
## 
## Coefficients:
##                           Estimate Std. Error t value Pr(>|t|)    
## (Intercept)             -0.3428725  0.0226302 -15.151  < 2e-16 ***
## log(total_length)        1.0047415  0.0015710 639.562  < 2e-16 ***
## cladeActinopterygii      0.1267135  0.0219543   5.772 8.76e-09 ***
## cladeChondrichthyes      0.0477143  0.0221117   2.158   0.0310 *  
## cladePetromyzontiformes  0.0038649  0.0253193   0.153   0.8787    
## cladeSarcopterygii       0.0866759  0.0269157   3.220   0.0013 ** 
## habitatbenthic           0.0277006  0.0047474   5.835 6.03e-09 ***
## habitatneritic           0.0001266  0.0033331   0.038   0.9697    
## habitatpelagic           0.0214364  0.0035927   5.967 2.74e-09 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.06194 on 2662 degrees of freedom
##   (6 observations deleted due to missingness)
## Multiple R-squared:  0.9963, Adjusted R-squared:  0.9963 
## F-statistic: 8.94e+04 on 8 and 2662 DF,  p-value: < 2.2e-16

Based on this, setting Placodermi as the base level for clade and the limited evidence we have for precaudal/total lengths in arthrodires, chondrichthyans seem like a suitable model to calculate fork and precaudal length in arthrodires, but actinopterygians show a comparatively shorter caudal fin and longer precaudal region than arthrodires.

total_length_to_standard_length<-data_final%>%
  filter(length_as=="total length")%>%
  filter(!is.na(precaudal_length))%$%
  lm(log(precaudal_length)~log(total_length)+(clade=="Placodermi"|clade=="Chondrichthyes")+habitat+shape,.)

summary(total_length_to_standard_length)
## 
## Call:
## lm(formula = log(precaudal_length) ~ log(total_length) + (clade == 
##     "Placodermi" | clade == "Chondrichthyes") + habitat + shape, 
##     data = .)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.68971 -0.02900 -0.00119  0.03513  0.26484 
## 
## Coefficients:
##                                                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                                           -0.2093917  0.0053967 -38.800  < 2e-16 ***
## log(total_length)                                      1.0026170  0.0015310 654.879  < 2e-16 ***
## clade == "Placodermi" | clade == "Chondrichthyes"TRUE -0.0742912  0.0035015 -21.217  < 2e-16 ***
## habitatbenthic                                         0.0133753  0.0050245   2.662  0.00781 ** 
## habitatneritic                                         0.0005698  0.0032508   0.175  0.86086    
## habitatpelagic                                         0.0195545  0.0035915   5.445 5.67e-08 ***
## shapeanguilliform                                     -0.0112573  0.0081300  -1.385  0.16627    
## shapecompressiform                                    -0.0209863  0.0040400  -5.195 2.21e-07 ***
## shapeelongate                                          0.0240499  0.0036321   6.621 4.29e-11 ***
## shapeflattened                                         0.0745609  0.0139088   5.361 9.00e-08 ***
## shapemacruriform                                      -0.1160331  0.0107117 -10.832  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.06052 on 2660 degrees of freedom
##   (6 observations deleted due to missingness)
## Multiple R-squared:  0.9965, Adjusted R-squared:  0.9964 
## F-statistic: 7.491e+04 on 10 and 2660 DF,  p-value: < 2.2e-16

16.1.3 Predicting total length in placoderms in this study assuming fusiform body shape and model with variable slope for clade

fossil_taxa<-fossil_taxa%>%
  mutate(shape="fusiform")%>%
  mutate(total_length=ifelse(is.na(total_length),
                             exp(predict(fit.shape_clade3,.))*
                                   regression.stats(fit.shape_clade3)$CF,
                             total_length))%>%
  mutate(precaudal_length=ifelse(is.na(precaudal_length),
                                 exp(predict(total_length_to_standard_length,.))*
           regression.stats(total_length_to_standard_length)$CF,
           precaudal_length),
         fork_length=ifelse(is.na(fork_length),
                            exp(predict(total_length_to_fork_length,.))*
           regression.stats(total_length_to_fork_length)$CF,
           fork_length))

16.2 Estimating body mass in Dunkleosteus using a modified ellipsoid equation

The length-weight equation in this study expands on the methodology of Ault and Luo (2013). Ault and Luo (2013) noted that the body of the fish is best modeled as an ellipsoid, with the equation:

\[ weight = girth^2*length + girth*length + girth^2 + girth + length \]

leaving five coefficients to be solved for experimentally.

To this I add three other parameters. Although a fish’s body is best modeled as an ellipsoid, it is an asymmetrical ellipsoid, with one bulbous half (encompassing the head region) and one thinner, more tapering half (including posterior half of the body tapering to the caudal peduncle). These must be accounted for separately to accurately estimate body mass, especially in an interspecific model, as the proportion of the thicker head region to the thinner abdomen and trunk varies among fishes. E.g., Dunkleosteus has an extremely thick head and likely had a strongly tapering caudal region given its pelagic habits (Carr, 2010), and cannot be modeled as a simple ellipsoid.

Additionally, the caudal fin contributes a small but non-insignificant amount to body mass. This is expected to be greater in taxa with internally heterocercal tails than those in which the tail is supported only by lepidotrichia. As a result, the model was fit with an additional parameter for caudal fins, with a variable level for taxa with internally heterocercal tails (Chondrichthyes, Placodermi, Sarcopterygii, and basal actinopterygian lineages like Polypteriformes, Amiiformes, Lepisosteiformes, and Acipenseriformes) where the spinal cord extends into the caudal fin, and those with internally homocercal tails (Teleostei) where it is truncated.

Taxa that lacked a distinct caudal fin and pre-caudal region (Anguilliformes, Mola) had to be excluded from the model, as their lack of a caudal fin resulted in a significant bias in body mass estimates when applied to fossil taxa.

Another potential source of error in body mass estimates of fishes is the swim bladder. Because the swim bladder is a large, gas-filled organ, this reduces the total density of the fish, and is expected to make fish with swim bladders comparatively lighter at the same volume than those without (Alexander, 1967). Fish without swim bladders include Petromyzontiformes and Chondrichthyans, but also most scombrids (but not istiophoriforms; see McCune and Carlson 2004 for a more comprehensive list of swim bladder loss in fishes). Whether arthrodires had a swim bladder is unknown: the frequently cited presence of lungs in the antiarch Bothriolepis appears to be a paleontological misinterpretation (see Goujet 2011 and Trinajstic et al. 2022) but there is circumstantial evidence based on taphonomy and body shape that Dunkleosteus did not have an enlarged liver similar to extant sharks and instead possessed some form of gas-filled floatation organs (Carr 2010; Engelman pers obs.). For the purposes of this study both were reported here, but the more conservative interpretation of Dunkleosteus without a swimbladder was considered the closer to the actual values.

The list of actinopterygian taxa considered as lacking the swim bladder was taken from McCune and Carlson (2004), Collette and Nauen (1983), and Bone (1972). All extant non-osteichthyan taxa were considered as lacking the swim bladder. The Dipnoi were coded as having swim bladders and the Latimeriidae coded as absent; in coelocanths the ancestral swim-bladder has been converted into a fat-filled organ (Cupello et al. 2015). No attempt was made to account for variation in the size of the swim bladder between fishes.

Girth was not available for all fishes, but body_width and body_depth were. There is no easy way to calculate the perimeter of an ellipse, but one commonly used way to do so is Ramanujan’s approximation (Ramanujan 1962; Villarino 2008), which is defined as…

\[ perimeter = π * [(a+b)+\frac{3*(a-b)^2)}{10(a+b)+\sqrt{a^2+14ab+b^2}}] \]

In which \(a\) is the radius of the major (longest) axis of the ellipse and \(b\) is the radius of the minor (i.e., the axis perpendicular to the major axis). In a fish, the major axis of the body in frontal view is almost always the dorsoventral axis, with the mediolateral axis being much narrower (especially in actinopterygians).

Thus, a user-defined formula was written in R to calculate girth based on Ramanujan’s approximation, assuming \(\frac{\text{body_depth}}{2}\) represented the radius of the major axis and \(\frac{\text{body_width}}{2}\) represented the radius of the minor.

#Testing if approximation formula works
fossil_taxa%>%
  mutate(girth2=ramanujan.approx(body_depth,body_width))%>%
  drop_na(girth,girth2)%>%
  select(taxon,specimen,girth,girth2)%>%
  remove_rownames()%>%
  kable(digits=1,col.names=c("Taxon","Specimen","Measured Girth","Estimated Girth Using Ramanujan Approximation"),
        caption="Testing Ramanujan's formula for approximating the perimeter of an ellipse on arthrodire specimens for which girth could be directly measured. All measurements in cm.")%>%
  column_spec(1, italic = T)%>%
  kable_styling()
Table 16.1: Testing Ramanujan’s formula for approximating the perimeter of an ellipse on arthrodire specimens for which girth could be directly measured. All measurements in cm.
Taxon Specimen Measured Girth Estimated Girth Using Ramanujan Approximation
Harrytoombsia elegans WAM P50914 23.1 21.4
Camuropiscis concinnus WAM P50976 13.0 11.4
Compagopiscis croucheri P50942 20.8 19.3
Coccosteus cuspidatus Recon. (M & W 1968) 24.8 24.1
Heintzichthys gouldii NHMUK PV P 9335 77.7 74.6
Dunkleosteus terrelli CMNH 7424 147.9 122.2
Dunkleosteus terrelli CMNH 6090 223.0 210.1
Dunkleosteus terrelli CMNH 7054 215.3 207.0
Dunkleosteus terrelli CMNH 5768 312.7 327.8

The actual values of girth are slightly higher than the approximation. This implies that arthrodires are slightly “blockier” than would be expected if modeled by a perfect ellipse. However, this should prove sufficient for estimating girth in fish specimens for which body_depth and body_width is available but girth was not reported or measurable, most of which are much less blocky than arthrodires.

fit.ellipsoid<-
  data_final%>%
       filter(length_as=="total length",
              total_length!=precaudal_length)%>%
       drop_na(precaudal_length)%>%
       mutate(girth=ifelse(!is.na(girth),girth,
                           ramanujan.approx(body_depth,body_width)))%$%
  lm(log(body_mass)~I(log(girth)^2)*log(precaudal_length)+swimbladder+
       log(girth)*log(precaudal_length)+
       log(precaudal_length)*log(head_length)-log(head_length)+
       heterocercal:log(total_length-precaudal_length),
     .)
fit.ellipsoid_no_swimbladder<-
  data_final%>%
       filter(length_as=="total length",
              total_length!=precaudal_length)%>%
       drop_na(precaudal_length)%>%
       mutate(girth=ifelse(!is.na(girth),girth,
                           ramanujan.approx(body_depth,body_width)))%$%
  lm(log(body_mass)~I(log(girth)^2)*log(precaudal_length)+
       log(girth)*log(precaudal_length)+
       log(precaudal_length)*log(head_length)-log(head_length)+
       heterocercal:log(total_length-precaudal_length),
     .)
summary(fit.ellipsoid)
## 
## Call:
## lm(formula = log(body_mass) ~ I(log(girth)^2) * log(precaudal_length) + 
##     swimbladder + log(girth) * log(precaudal_length) + log(precaudal_length) * 
##     log(head_length) - log(head_length) + heterocercal:log(total_length - 
##     precaudal_length), data = .)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -1.7960 -0.1623 -0.0075  0.1562  1.2890 
## 
## Coefficients:
##                                                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                                            -3.779040   0.184857 -20.443  < 2e-16 ***
## I(log(girth)^2)                                         0.335456   0.025047  13.393  < 2e-16 ***
## log(precaudal_length)                                   2.374638   0.087108  27.261  < 2e-16 ***
## swimbladderTRUE                                         0.100278   0.045260   2.216   0.0269 *  
## log(girth)                                              0.914463   0.113449   8.061 2.15e-15 ***
## I(log(girth)^2):log(precaudal_length)                   0.008503   0.005037   1.688   0.0917 .  
## log(precaudal_length):log(girth)                       -0.529970   0.041916 -12.644  < 2e-16 ***
## log(precaudal_length):log(head_length)                  0.116825   0.015195   7.688 3.55e-14 ***
## heterocercalFALSE:log(total_length - precaudal_length)  0.176658   0.032178   5.490 5.09e-08 ***
## heterocercalTRUE:log(total_length - precaudal_length)   0.180682   0.029982   6.026 2.35e-09 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2858 on 1003 degrees of freedom
##   (1649 observations deleted due to missingness)
## Multiple R-squared:  0.9915, Adjusted R-squared:  0.9915 
## F-statistic: 1.304e+04 on 9 and 1003 DF,  p-value: < 2.2e-16
regression.stats(fit.ellipsoid)

The ellipsoid model predicts body mass very well, considering it is an interspecific model based on all fishes and many of the data were estimated based on species-specific length-weight equations. Additionally, the two extra parameters added (ratio of head length to precaudal length, caudal fin size, and presence of swim bladder) explained statistically detectable amounts of variation in the model. There are probably more precise ways to model the body mass of Dunkleosteus (e.g., a volumetric model; Brassey 2016), but for the purposes of this study this seems reliable enough.

rbind(
  model1=(lm(log(body_mass)~I(log(girth)^2)*log(precaudal_length)+
       log(girth)*log(precaudal_length)+
       log(precaudal_length)*log(head_length)-log(head_length)+
       (clade!="Actinopterygii"|higher_group=="Basal Actinopterygii"):log(total_length-precaudal_length),
     data_final%>%drop_na(girth,body_depth,body_width)%>%
       mutate(precaudal_length=ifelse(is.na(precaudal_length)|
                                        total_length==precaudal_length,
                                      total_length-1,
                                      precaudal_length))))%>%
    regression.stats(.),
  model2=(lm(log(body_mass)~I(log(girth)^2)*log(precaudal_length)+
       log(girth)*log(precaudal_length)+
       log(precaudal_length)*log(head_length)-log(head_length)+
       (clade!="Actinopterygii"|higher_group=="Basal Actinopterygii"):log(total_length-precaudal_length),
     data_final%>%drop_na(girth,body_depth,body_width)%>%
       mutate(precaudal_length=ifelse(is.na(precaudal_length)|
                                        total_length==precaudal_length,
                                      total_length-1,
                                      precaudal_length),
              girth=ramanujan.approx(body_depth,body_width))))%>%
    regression.stats(.))%>%
  `rownames<-`(c("Model Using Directly Measured Girth (when available)",
                "Model Using Girth Estimated from Depth and Width"))

A model using the directly-measured value for girth, when it was directly measured in the specimens in question (usually in the collections of the CMNH, FSBC, and OSUM) produced higher errors and worse AIC and BIC values when all other variables are held equal. This is likely to be due to measurement error in measuring girth rather than modelling it based on body_width and body_depth.

However, measured girth should still be accurate for arthrodires because the issues with girth were primarily driven by the difficulties of measuring girth in fluid-preserved fish, rather than fossilized specimens.

fossil_taxa<-fossil_taxa%>%
  mutate(girth3=girth,
         girth=ifelse(is.na(girth),ramanujan.approx(body_depth,body_width),girth))%>%
  augment(fit.ellipsoid,
          newdata=.,
          interval="predict")%>%
  mutate(girth=girth3)%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.ellipsoid)$CF))%>%
  mutate(body_mass=.fitted,
         mass1.lower=.lower,
         mass1.upper=.upper)

dunk_weights<-fossil_taxa%>%
  mutate(swimbladder=T,
         girth=ifelse(is.na(girth),ramanujan.approx(body_depth,body_width),girth))%>%
  augment(fit.ellipsoid,
          newdata=.,
          interval="predict")%>%
  mutate(swimbladder=F)%>%
  rename_at(vars(starts_with('.')), funs(paste0("mass2",.)))%>%
  mutate(across(mass2.fitted:mass2.upper,~exp(.)*regression.stats(fit.ellipsoid)$CF))%>%
  augment(fit.ellipsoid_no_swimbladder,
          newdata=.,
          interval="predict")%>%
  mutate(girth=girth3)%>%
  rename_at(vars(starts_with('.')), funs(paste0("mass3",.)))%>%
  mutate(across(mass3.fitted:mass3.upper,~exp(.)*regression.stats(fit.ellipsoid_no_swimbladder)$CF))%>%
  drop_na(body_mass)%>%
  arrange(body_mass)%>%
  mutate(across(c(body_mass,mass1.lower,mass1.upper,mass2.fitted,mass2.lower,mass2.upper,
                  mass3.lower,mass3.upper,mass3.fitted),~./1000),
         mass1.range=paste0("(",round(mass1.lower,3),"–",round(mass1.upper,3),")"),
         mass2.range=paste0("(",round(mass2.lower,3),"–",round(mass2.upper,3),")"),
         mass3.range=paste0("(",round(mass3.lower,3),"–",round(mass3.upper,3),")"))%>%
  select(taxon,specimen,total_length,mass3.fitted,mass3.range,body_mass,mass1.range,mass2.fitted,mass2.range)
dunk_weights%>%
  kable(digits=c(1,1,1,3,3,3,3,3,3),align=c("l","c","c","c","c","c","c","c","c"),
        col.names=c("Taxon","Specimen","Total Length",
                    "Est.","95% P.I.",
                    "Est.","95% P.I.",
                    "Est.","95% P.I."),
        caption="Body masses of arthrodires estimated using a multivariate model. Body masses reported in kg, but with three digits in order to display estimated masses for smaller arthrodire taxa.")%>%
  add_header_above(c(" "=3,"Omitting swimbladder as explanatory variable"=2,"Assuming no swim bladder"=2,"Assuming presence of swim bladder"=2))%>%
  column_spec(1,italic=T)%>%
  kable_styling()
Table 16.2: Body masses of arthrodires estimated using a multivariate model. Body masses reported in kg, but with three digits in order to display estimated masses for smaller arthrodire taxa.
Omitting swimbladder as explanatory variable
Assuming no swim bladder
Assuming presence of swim bladder
Taxon Specimen Total Length Est. 95% P.I. Est. 95% P.I. Est. 95% P.I.
Millerosteus minor Composite Millerosteus 14.9 0.037 (0.021–0.065) 0.035 (0.02–0.061) 0.039 (0.022–0.068)
Latocamurus coulthardi WAM 86.9.699 24.0 0.086 (0.049–0.152) 0.083 (0.047–0.146) 0.092 (0.052–0.161)
Camuropiscis concinnus WAM P50976 30.0 0.175 (0.1–0.307) 0.169 (0.096–0.296) 0.187 (0.106–0.328)
Incisoscutum ritchei Recon. (Trinajstic 2013) 30.3 0.197 (0.112–0.347) 0.191 (0.109–0.336) 0.211 (0.12–0.373)
Torosteus pulchellus Composite pulchellus 36.0 0.335 (0.191–0.589) 0.326 (0.186–0.573) 0.361 (0.205–0.636)
Mcnamaraspis kaprios WAM 86.9.676 34.6 0.347 (0.197–0.609) 0.337 (0.192–0.592) 0.372 (0.211–0.657)
Compagopiscis croucheri WAM 70.4.263 40.3 0.401 (0.228–0.704) 0.392 (0.223–0.688) 0.433 (0.245–0.763)
Compagopiscis croucheri P50942 37.9 0.434 (0.247–0.763) 0.423 (0.241–0.742) 0.467 (0.265–0.824)
Harrytoombsia elegans WAM P50914 37.0 0.476 (0.271–0.837) 0.463 (0.264–0.814) 0.512 (0.29–0.903)
Coccosteus cuspidatus Recon. (M & W 1968) 39.4 0.566 (0.322–0.995) 0.553 (0.314–0.971) 0.611 (0.346–1.078)
Torosteus tuberculatus Composite tuberculatus 40.2 0.609 (0.346–1.071) 0.594 (0.338–1.043) 0.656 (0.372–1.158)
Plourdosteus canadensis MNHM 2-177 37.5 0.784 (0.446–1.378) 0.757 (0.431–1.33) 0.837 (0.475–1.473)
Gymnotrachelus hydei Recon. (Carr 1994) 74.4 2.144 (1.22–3.77) 2.127 (1.211–3.736) 2.352 (1.331–4.156)
Holonema westolli Recon. (Miles 1971) 60.6 3.189 (1.809–5.624) 3.145 (1.785–5.541) 3.477 (1.963–6.157)
Eastmanosteus calliaspis 70.4.864 79.3 4.991 (2.834–8.79) 4.951 (2.814–8.71) 5.473 (3.092–9.688)
Paramylostoma arcualis CMNH 6054 84.6 5.147 (2.926–9.054) 5.104 (2.904–8.968) 5.642 (3.191–9.974)
Heintzichthys gouldii NHMUK PV P 9335 113.1 16.281 (9.233–28.708) 16.246 (9.224–28.615) 17.960 (10.127–31.853)
Bungartius perissus CMNH 7573 179.8 57.403 (32.641–100.951) 57.394 (32.672–100.825) 63.448 (35.858–112.266)
Dunkleosteus terrelli CMNH 7424 188.9 106.270 (60.137–187.792) 106.733 (60.464–188.408) 117.991 (66.325–209.905)
Dunkleosteus terrelli CMNH 7054 295.5 377.371 (213.036–668.473) 381.415 (215.534–674.962) 421.646 (236.229–752.595)
Dunkleosteus terrelli CMNH 6090 283.3 387.796 (218.669–687.732) 391.650 (221.066–693.864) 432.960 (242.322–773.577)
Dunkleosteus terrelli CMNH 5768 340.7 997.161 (557.764–1782.708) 1008.407 (564.623–1800.995) 1114.771 (618.875–2008.022)

Unusually, the presence of a swim bladder seems to predict higher body masses in fishes, contrary to theoretical expectations (Alexander 1967). One possibility is this might be due to a large, oil-filled liver in sharks, which occupies most of the gut cavity and can represent 20-25% of total body mass in many pelagic species (Corner et al. 1969, Kauffman 1950, De Maddalena et al. 2003). Shark liver is much denser than a swim bladder (0.9 versus 0.00122 g/cm3; Baldridge 1970), but because it occupies a proportionally greater volume of the body cavity it results in the density of the entire animal being lower. This is similar to how in birds and sauropod dinosaurs whole-body density is only about 0.7 g/cm3 due to the presence of extensive air sacs and pneumaticity (Wedel 2009), despite the rest of the animal’s tissues being comparably in density to other tetrapods.

It is not clear if arthrodires should be treated as having a liver comparable in size to sharks. Preserved liver remains have been documented for Incisoscutum ritchei (see Trinajstic et al. 2022), but these specimens suggest a liver far smaller than seen in extant sharks (Engelman, unpublished data in prep). Because of this uncertainty (and to limit the scope of the present contribution), the author prefers to leave the question of liver size alone for now and merely consider the present or absence of a swim bladder, as the absence of this organ is better-established in arthrodires (Goujet 2011, Trinajstic et al. 2022)

16.2.1 Estimating mass using only large, pelagic fishes

Because Dunkleosteus and Heintzichthys are so similar in length-weight relationship to lamnid sharks and tunas, it is worth considering how an equation using only large, pelagic fishes estimates body mass in these taxa. This equation considers three main groups: Scombridae, Lamniformes, and Istiophoriformes. For this model fork length was used instead of precaudal length, as fork length is the typical unit of measurement in these three fish groups and often requires no estimation (contra precaudal/standard length, which for tunas often encompasses most of the caudal fin due to the morphology of the peduncle). No compensation was made for caudal fin structure in this model due to using fork length.

Note: Mitsukurina owstoni was excluded because it is a bathydemersal taxon with a long caudal fin and lacks fork length, and trying to back-calculate fork length and add Mitsukurina to this analysis results in massive error for M. owstoni

fit.ellipsoid_pelagic<-
  lm(log(body_mass)~I(log(girth)^2)*log(fork_length)+
       log(girth)*log(fork_length)+swimbladder+
       log(fork_length)*log(head_length)-log(head_length),
     data_final%>%
       filter(family %in% c("Scombridae")|
                order %in% c("Lamniformes","Istiophoriformes"),
              !genus %in% c("Mitsukurina"))%>%
       filter(length_as=="total length")%>%
       mutate(fork_length=ifelse(is.na(fork_length)|
                                        total_length==precaudal_length,
                                      total_length-1,
                                      fork_length),
              girth=ifelse(!is.na(girth),girth,
                           ramanujan.approx(body_depth,body_width))))
summary(fit.ellipsoid_pelagic)
## 
## Call:
## lm(formula = log(body_mass) ~ I(log(girth)^2) * log(fork_length) + 
##     log(girth) * log(fork_length) + swimbladder + log(fork_length) * 
##     log(head_length) - log(head_length), data = data_final %>% 
##     filter(family %in% c("Scombridae") | order %in% c("Lamniformes", 
##         "Istiophoriformes"), !genus %in% c("Mitsukurina")) %>% 
##     filter(length_as == "total length") %>% mutate(fork_length = ifelse(is.na(fork_length) | 
##     total_length == precaudal_length, total_length - 1, fork_length), 
##     girth = ifelse(!is.na(girth), girth, ramanujan.approx(body_depth, 
##         body_width))))
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.54702 -0.06120  0.00738  0.07700  0.67936 
## 
## Coefficients:
##                                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                        9.01229    3.40056   2.650 0.008736 ** 
## I(log(girth)^2)                    1.27201    0.22869   5.562 9.17e-08 ***
## log(fork_length)                   0.29833    0.68882   0.433 0.665439    
## log(girth)                        -7.08215    1.75850  -4.027 8.21e-05 ***
## swimbladderTRUE                   -0.36946    0.03997  -9.242  < 2e-16 ***
## I(log(girth)^2):log(fork_length)  -0.18821    0.03895  -4.832 2.82e-06 ***
## log(fork_length):log(girth)        1.15934    0.31480   3.683 0.000302 ***
## log(fork_length):log(head_length) -0.02306    0.02415  -0.955 0.340869    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1465 on 186 degrees of freedom
##   (221 observations deleted due to missingness)
## Multiple R-squared:  0.9907, Adjusted R-squared:  0.9904 
## F-statistic:  2840 on 7 and 186 DF,  p-value: < 2.2e-16
regression.stats(fit.ellipsoid_pelagic)
fossil_taxa%>%
  mutate(girth2=girth,
         girth=ifelse(!is.na(girth),girth,
                      ramanujan.approx(body_depth,body_width)))%>%
  augment(fit.ellipsoid_pelagic,
          newdata=.,
          interval="predict")%>%
  mutate(girth=girth2)%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.ellipsoid_pelagic)$CF))%>%
  mutate(body_mass=.fitted)%>%
  drop_na(body_mass)%>%
  filter(genus %in% c("Dunkleosteus","Heintzichthys"))%>%
  arrange(body_mass)%>%
  mutate(across(c(body_mass,.lower,.upper),~./1000))%>%
  select(taxon,specimen,total_length,body_mass,.lower,.upper)%>%
  kable(digits=1,col.names=c("Taxon","Specimen","Total Length","Est.","Lower 95% P.I.","Upper 95% P.I."),
        caption="Body masses of pelagic arthrodires estimated using a multivariate model based on the three major living groups of fusiform, pelagic fishes (Lamniformes, Scombridae, and Istiophoriformes). Body masses reported in kg.")%>%
  kable_styling()
Table 16.3: Body masses of pelagic arthrodires estimated using a multivariate model based on the three major living groups of fusiform, pelagic fishes (Lamniformes, Scombridae, and Istiophoriformes). Body masses reported in kg.
Taxon Specimen Total Length Est. Lower 95% P.I. Upper 95% P.I.
Heintzichthys gouldii NHMUK PV P 9335 113.1 23.6 17.2 32.2
Dunkleosteus terrelli CMNH 7424 188.9 166.7 120.7 230.1
Dunkleosteus terrelli CMNH 7054 295.5 545.0 393.0 755.8
Dunkleosteus terrelli CMNH 6090 283.3 561.3 401.8 784.2
Dunkleosteus terrelli CMNH 5768 340.7 1204.1 833.1 1740.5

16.3 Length-weight relationships in arthrodires

16.3.1 All fishes

ggplot(data_final%>%
         filter(length_as!="estimated t.l."|clade=="Placodermi",
                is_body_mass_estimated==F|!is.na(girth)|!is.na(body_width)&!is.na(body_depth),
                !(total_length==precaudal_length)),
       aes(y=body_mass,x=total_length))+
  geom_star(aes(starshape=clade,fill=clade))+
  geom_smooth(formula=y~x,method="lm")+
  geom_star(data=fossil_taxa%>%
              mutate(girth=ifelse(is.na(girth),ramanujan.approx(body_depth,body_width),girth))%>%
              augment(fit.ellipsoid,
                      newdata=.,
                      interval="predict")%>%
              mutate(body_mass=exp(.fitted)*regression.stats(fit.ellipsoid)$CF),
            aes(starshape=clade,fill=clade),color="white",
            size=3.5,show.legend=F)+
  scale_starshape_manual(values=c(15,13,11,1,28))+
  scale_fill_manual(values=c(hue_pal()(4)[1:3],"black",hue_pal()(4)[4]))+
  scale_x_continuous(trans='log10')+
  scale_y_continuous(trans="log10",
                     breaks = trans_breaks("log10", function(x) 10^x,n=8),
                     labels = trans_format("log10", math_format(10^.x)))+
  labs(y="Body Mass (g)",x="Total Length (cm)",fill="Clade",starshape="Clade")+
  theme(legend.position=c(0.8,0.2))
Plot of length versus weight across all fishes with weight data in this study, showing how fishes in general follow a consistent length-weight relationship (with significant interspecific variation; Froese 2006) and that predicted body masses for complete arthrodires fall within the range of variation seen in modern fishes. Regression line represents the simple length-weight relationship for all fishes, not the best fit curve for the ellipsoid model.

Figure 16.1: Plot of length versus weight across all fishes with weight data in this study, showing how fishes in general follow a consistent length-weight relationship (with significant interspecific variation; Froese 2006) and that predicted body masses for complete arthrodires fall within the range of variation seen in modern fishes. Regression line represents the simple length-weight relationship for all fishes, not the best fit curve for the ellipsoid model.

Dunkleosteus has a length-weight relationship that is on the edge of the range of variation of fishes, but this is to be expected given the overall short, deep body of this taxon. For example coelocanths (Latimeria spp.), which are also very deep-bodied and heavy relative to their length, show a similar length-weight relationship.

Note that the lower regression line for elasmobranchs is not strictly due to clade-specific differences in body mass relative to the physical dimensions of these animals, but due to the fact that elasmobranchs in general tend to be much longer for their weight relative to osteichthyans and placoderms. This can be seen in the fact that elasmobranch chondrichthyans and “elongate-bodied” osteichthyans plot along similar lines, though elasmobranchs tend to show larger heads relative to osteichthyans with elongate trunks because an aspect ratio that would be generalized fusiform for elasmobranchs is elongate for actinopterygians due to differences in body proportions between the clades (see manuscript).

Similarly, compressiform fishes show higher length-weight relationships than typical fishes. All of this is to be expected. Fishes with long, narrow bodies are going to be lighter at the same unit length as a fusiform fish, and short, deep-bodied fish are going to be heavier.

Overall, what this means is that it is safe to use this model to estimate the body mass of arthrodires, because the differences between elasmobranchs and osteichthyans here are due to physical proportions, which are accounted for by the girth measurements in the ellipsoid model, rather than due to a difference that cannot be measurably quantified by this dataset.

ggplot(data_final%>%
         filter(length_as!="estimated t.l."|clade=="Placodermi",
                is_body_mass_estimated==F|!is.na(girth)|!is.na(body_width)&!is.na(body_depth),
                !(total_length==precaudal_length))%>%
         mutate(clade=ifelse(clade %in% c("Actinopterygii","Sarcopterygii"),
                             "Osteichthyes",clade),
                shape2=case_when(clade=="Chondrichthyes"~"Chondrichthyes",
                                 shape=="fusiform"~"fusiform",
                                 shape %in% c("anguilliform","elongate")~"anguilliform/elongate",
                                 shape=="compressiform" ~ "compressiform"))%>%
         mutate(shape2=factor(shape2,ordered=T,levels=c("Chondrichthyes",
                                                        "anguilliform/elongate",
                                                        "fusiform",
                                                        "compressiform")))%>%
         filter(shape!="flattened",!(clade %in% c("Petromyzontiformes","Placodermi")),
                shape!="macruriform"|clade=="Chondrichthyes")%>%
         drop_na(body_mass),
       aes(y=body_mass,x=total_length))+
  geom_star(aes(starshape=clade,fill=shape2))+
  geom_star(data=.%>%filter(clade=="Chondrichthyes"),
            aes(starshape=clade,fill=shape2),size=2.5,show.legend=F)+
  geom_smooth(formula=y~x,method="lm",aes(color=shape2))+
  geom_star(data=.%>%filter(taxon=="Coccosteus cuspidatus"),
            aes(starshape=clade,fill=shape2))+
  scale_starshape_manual(values=c(11,15),guide="none")+
  scale_fill_manual(values=c(hue_pal()(4)))+
  scale_color_manual(values=c(hue_pal()(4)))+
  scale_x_continuous(trans='log10')+
  scale_y_continuous(trans="log10",
                     breaks = trans_breaks("log10", function(x) 10^x,n=8),
                     labels = trans_format("log10", math_format(10^.x)))+
  labs(y="Body Mass (g)",x="Total Length (cm)",fill="Group",starshape="Clade",color="Group")+
  theme(legend.position=c(0.8,0.25))+
  guides(fill = guide_legend(override.aes = list(starshape = c(11,15,15,15))))
Plot of length versus weight in fishes, showing that sharks and actinopterygians with elongate trunks (either elongate or anguilliform) plot along similar regression lines.

Figure 16.2: Plot of length versus weight in fishes, showing that sharks and actinopterygians with elongate trunks (either elongate or anguilliform) plot along similar regression lines.

16.3.2 Comparing arthrodires to tunas and sharks

(arthrodire_length_weight<-ggplot(data_final%>%
         filter(is_body_mass_estimated==F|specimen=="MZL 23981", #MZL 23981 was not dropped because although its weight was estimated this value was very close to the reported total weight
                !(body_mass==1980000&genus=="Cetorhinus"), #This individual was dropped because Springer and Gilbert (1976) noted it to be unusually underweight and thin
                clade=="Chondrichthyes"|genus %in% c("Thunnus",
                                                     "Allothunnus",
                                                     "Auxis",
                                                     "Euthynnus",
                                                     "Katsuwonus"),
                !order %in% c("Rajiformes","Rhinopristiformes","Chimaeriformes"),
                !genus %in% c("Alopias"))%>%
         bind_rows(.,fossil_taxa%>%filter(!is.na(body_width),clade=="Placodermi"))%>%
         drop_na(body_mass,total_length)%>%
         mutate(clade=factor(case_when(clade=="Actinopterygii"~"Thunnini",
                                family=="Lamnidae"~"Lamnidae",
                                genus=="Oxynotus"~"Oxynotus",
                                clade=="Chondrichthyes"~"Other Elasmobranchii",
                                clade=="Placodermi"~"Arthrodira"),
                             ordered=T,
                             levels=c("Arthrodira","Lamnidae","Oxynotus",
                                      "Other Elasmobranchii","Thunnini")))%>%
         arrange(desc(clade)),
       aes(y=body_mass,x=total_length))+
  geom_smooth(aes(color=clade),method="lm",formula=y~x,alpha=0.25)+
  geom_star(aes(starshape=clade,fill=clade),size=3)+
  scale_starshape_manual(values=c(1,13,13,13,15),
                         labels=c("Arthrodira","Lamnidae","*Oxynotus*",
                                  "Other Chondrichthyes","Thunnini"))+
  scale_color_manual(values=c(hue_pal()(4)[1:2],"tan",hue_pal()(4)[3:4]),
                     labels=c("Arthrodira","Lamnidae","*Oxynotus*",
                              "Other Chondrichthyes","Thunnini"))+
  scale_fill_manual(values=c(hue_pal()(4)[1:2],"tan",hue_pal()(4)[3:4]),
                    labels=c("Arthrodira","Lamnidae","*Oxynotus*",
                             "Other Chondrichthyes","Thunnini"))+
  geom_smooth(aes(color=clade),method="lm",formula=y~x,se=F,show.legend=F)+
  geom_smooth(data=.%>%filter(clade=="Arthrodira"),
              aes(color=clade),method="lm",formula=y~x,se=F,show.legend=F)+
  geom_errorbar(data=.%>%filter(clade=="Arthrodira"),
                aes(ymin=mass1.lower,ymax=mass1.upper),width=.02)+
  geom_star(data=.%>%filter(clade=="Arthrodira"),
            aes(starshape=clade,fill=clade),size=3.5,show.legend = F)+
  scale_x_continuous(trans='log10')+
  scale_y_continuous(trans="log10",
                       breaks = trans_breaks("log10", function(x) 10^x,n=8),
                       labels = trans_format("log10", math_format(10^.x)))+
  labs(y="Body Mass (g)",x="Total Length (cm)",fill="Clade",starshape="Clade",color="Clade")+
  theme_classic()+
  theme(legend.position = c(0.8,0.25),
        legend.text=element_markdown()))
Graph of total length versus body mass for all sharks and tunas (Thunnini) in which weight was directly recorded, compared to estimated weights for arthrodires in this study

Figure 16.3: Graph of total length versus body mass for all sharks and tunas (Thunnini) in which weight was directly recorded, compared to estimated weights for arthrodires in this study

ggsave("Figure 11 (Arthrodire Length-Weights).tiff",
       arthrodire_length_weight,device="tiff",compression="lzw",dpi=600,units="mm",width=165,height=110)
ggplot(data_final%>%
         filter(!(body_mass==1980000&genus=="Cetorhinus"), #This individual was dropped because Springer and Gilbert (1976) noted it to be unusually underweight and thin
                clade=="Chondrichthyes"|genus %in% c("Thunnus",
                                                     "Allothunnus",
                                                     "Auxis",
                                                     "Euthynnus",
                                                     "Katsuwonus"),
                !order %in% c("Rajiformes","Rhinopristiformes","Chimaeriformes"),
                !genus %in% c("Alopias"))%>%
         bind_rows(.,fossil_taxa%>%filter(!is.na(body_width),clade=="Placodermi"))%>%
         drop_na(body_mass,total_length)%>%
         mutate(clade=factor(case_when(clade=="Actinopterygii"~"Thunnini",
                                family=="Lamnidae"~"Lamnidae",
                                genus=="Oxynotus"~"Oxynotus",
                                clade=="Chondrichthyes"~"Other Elasmobranchii",
                                clade=="Placodermi"~"Arthrodira"),
                             ordered=T,
                             levels=c("Arthrodira","Lamnidae","Oxynotus",
                                      "Other Elasmobranchii","Thunnini")))%>%
         arrange(desc(clade)),
       aes(y=body_mass,x=total_length))+
  geom_smooth(aes(color=clade),method="lm",formula=y~x,alpha=0.25)+
  geom_star(aes(starshape=clade,fill=clade),size=3)+
  geom_smooth(aes(color=clade),method="lm",formula=y~x,alpha=0.25,show.legend=F)+
  geom_smooth(data=.%>%filter(clade=="Arthrodira"),
              aes(color=clade),method="lm",formula=y~x,se=F,show.legend=F)+
  geom_errorbar(data=.%>%filter(clade=="Arthrodira"),
                aes(ymin=mass1.lower,ymax=mass1.upper),width=.02)+
  geom_star(data=.%>%filter(clade=="Arthrodira"),
            aes(starshape=clade,fill=clade),size=3.5,show.legend = F)+
  scale_starshape_manual(values=c(1,13,13,13,15),
                         labels=c("Arthrodira","Lamnidae","*Oxynotus*",
                                  "Other Chondrichthyes","Thunnini"))+
  scale_color_manual(values=c(hue_pal()(4)[1:2],"tan",hue_pal()(4)[3:4]),
                     labels=c("Arthrodira","Lamnidae","*Oxynotus*",
                              "Other Chondrichthyes","Thunnini"))+
  scale_fill_manual(values=c(hue_pal()(4)[1:2],"tan",hue_pal()(4)[3:4]),
                    labels=c("Arthrodira","Lamnidae","*Oxynotus*",
                             "Other Chondrichthyes","Thunnini"))+
  scale_x_continuous(trans='log10')+
  scale_y_continuous(trans="log10",
                       breaks = trans_breaks("log10", function(x) 10^x,n=8),
                       labels = trans_format("log10", math_format(10^.x)))+
  labs(y="Body Mass (g)",x="Total Length (cm)",fill="Clade",starshape="Clade",color="Clade")+
  theme_classic()+
  theme(legend.position = c(0.8,0.2),
        legend.text=element_markdown())
Graph of total length versus body mass in arthrodires, sharks, and tunas (Thunnini), including specimens whose mass was estimated through length-weight equations

Figure 16.4: Graph of total length versus body mass in arthrodires, sharks, and tunas (Thunnini), including specimens whose mass was estimated through length-weight equations

Overall, this shows that arthrodires exhibit a length-weight relationship more similar to tunas than non-lamnid sharks, and are thus extremely heavy for their length. This includes non-pelagic taxa like Incisoscutum and Coccosteus. Later Devonian pelagic arthrodires (Heintzichthys, Dunkleosteus) show values more similar to tunas (Thunnini) and lamnid sharks, whereas geologically older species show values that are roughly intermediate between thunnins/lamnids and “typical” sharks like carcharhinids, hexanchiids, etc.

16.4 Estimating weight of Dunkleosteus based on length-weight equation of Carcharodon carcharias

dunk_weights_carcharodon<-fossil_taxa%>%
  left_join(.,dunkleosteus_jaws%>%select(specimen,JM1:inferognathal_length),by="specimen")%>%
  filter(genus=="Dunkleosteus")%>%
  mutate(JM5=ifelse(specimen=="CMNH 5768",mean(26.2,29.3),JM5))%>%
  mutate(JM5=ifelse(specimen=="CMNH 6090",mean(20.39,21.09),JM5))%>%
  mutate(JM5=ifelse(specimen=="CMNH 7054",mean(23.73,22.81),JM5))%>%
  mutate(total_length=exp(predict(fit.shape_clade3,.))*
           regression.stats(fit.shape_clade3)$CF)%>%
  mutate(precaudal_length=exp(predict(total_length_to_standard_length,.))*
           regression.stats(total_length_to_standard_length)$CF)%>%
  mutate(weight=45.98*((girth/100)^2*(precaudal_length/100))^0.9267,
         body_depth_ratio=body_depth/OOL,
         jawratio=body_depth/JM5)%>%
  filter(!is.na(weight))
dunk_weights_carcharodon%>%
  rownames_to_column()%>%
  select(specimen,OOL,body_depth_ratio,jawratio,total_length,precaudal_length,girth,weight)%>%
  arrange(weight)%>%
  kable(digits=1,
        align=c("l","c","c","c","c","c","c","c"),
        col.names=c("Specimen","OOL","Body Depth/OOL","Body Depth/JM5","Estimated Total Length","Estimated Precaudal Length","Girth","Estimated Body Mass"),
        caption="Estimated body masses for <i>Dunkleosteus terrelli</i>, assuming proportions similar to a great white shark (<i>Carcharodon carcharias</i>) and using the regression model of Mollet and Caillet (1996). All lengths in cm and all weights in kg")%>%
  kable_styling()
Table 16.4: Estimated body masses for Dunkleosteus terrelli, assuming proportions similar to a great white shark (Carcharodon carcharias) and using the regression model of Mollet and Caillet (1996). All lengths in cm and all weights in kg
Specimen OOL Body Depth/OOL Body Depth/JM5 Estimated Total Length Estimated Precaudal Length Girth Estimated Body Mass
CMNH 7424 29.0 1.3 3.1 188.9 147.2 147.9 136.0
CMNH 7054 45.5 1.6 3.1 295.5 230.6 215.3 413.2
CMNH 6090 43.6 1.7 3.7 283.3 221.0 223.0 423.9
CMNH 5768 52.5 2.2 4.4 340.7 265.9 312.7 941.5

16.5 Length-weight equations in Dunkleosteus

16.5.1 Using masses from ellipsoid model

dunk_length_weight<-lm(log(body_mass)~log(total_length),
                       fossil_taxa %>% filter(genus=="Dunkleosteus"))
summary(dunk_length_weight)
## 
## Call:
## lm(formula = log(body_mass) ~ log(total_length), data = fossil_taxa %>% 
##     filter(genus == "Dunkleosteus"))
## 
## Residuals:
##        5        8        9       11 
##  0.07779 -0.06327 -0.24051  0.22599 
## 
## Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)  
## (Intercept)        -7.1494     3.1209  -2.291   0.1491  
## log(total_length)   3.5581     0.5567   6.392   0.0236 *
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2439 on 2 degrees of freedom
##   (7 observations deleted due to missingness)
## Multiple R-squared:  0.9533, Adjusted R-squared:   0.93 
## F-statistic: 40.85 on 1 and 2 DF,  p-value: 0.02362

16.5.2 Using masses from Carcharodon model

summary(lm(log(weight)~log(total_length),dunk_weights_carcharodon))
## 
## Call:
## lm(formula = log(weight) ~ log(total_length), data = dunk_weights_carcharodon)
## 
## Residuals:
##        1        2        3        4 
##  0.06299 -0.04592 -0.20166  0.18459 
## 
## Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)  
## (Intercept)       -11.2720     2.5723  -4.382   0.0483 *
## log(total_length)   3.0758     0.4588   6.704   0.0215 *
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.201 on 2 degrees of freedom
## Multiple R-squared:  0.9574, Adjusted R-squared:  0.9361 
## F-statistic: 44.94 on 1 and 2 DF,  p-value: 0.02154

16.5.3 Length-Weight Curve for Dunkleosteus terrelli

lm_eqn.all <- function(plot_data){
  m <- dunk_length_weight;
  eq <- substitute(italic("body mass") == a %.% italic("total length")^b*","~~italic(r)^2~"="~r2, 
                   list(a = format(unname(exp(coef(m)[1])), digits = 5),
                        b = format(unname(coef(m)[2]), digits = 5),
                        r2 = format(summary(m)$r.squared, digits = 3)))
  as.character(as.expression(eq));
}
#Plotting length-weight curve
dunk_length_weight_graph<-ggplot(fossil_taxa%>%filter(genus=="Dunkleosteus")%>%drop_na(body_mass),
       aes(x=total_length,y=body_mass/1000))+
  geom_errorbar(aes(ymin=mass1.lower/1000,ymax=mass1.upper/1000),width=2)+
  geom_smooth(formula=y~I(x^dunk_length_weight$coefficients[2]),method="lm")+
  geom_point()+
  labs(x="Total Length (cm)",y="Body Mass (kg)")+
  geom_text(aes(x = 188, y = 2000, label = lm_eqn.all(plot_data)),hjust=0, 
            parse = TRUE,data.frame())+
  theme_cowplot()

(dunk_length_weight_graph2<-ggdraw() +
  draw_image("Devonian Fish Tale Supplementary File 9 (Big Dunk Silhouette).png",
             x=0.33,y=0.27,scale=0.16) +
  draw_image("Devonian Fish Tale Supplementary File 8 (Medium Dunk Silhouette).png",
             x=-0.010,y=0.06,scale=0.14) +
  draw_image("Devonian Fish Tale Supplementary File 7 (Little Dunk Silhouette).png",
             x=-0.3,y=0.02,scale=0.12) +
  draw_plot(dunk_length_weight_graph))
Length-weight curve for Dunkleosteus terrelli using the ellipsoid model, with silhouettes of D. terrelli at different life stages showing how this organism becomes deeper-bodied with growth. Total lengths and body masses are both estimates based on the present study.

Figure 16.5: Length-weight curve for Dunkleosteus terrelli using the ellipsoid model, with silhouettes of D. terrelli at different life stages showing how this organism becomes deeper-bodied with growth. Total lengths and body masses are both estimates based on the present study.

ggsave("Dunkleosteus Length-Weight.tiff",dunk_length_weight_graph2,
       device="tiff",width=165,height=124,units="mm",dpi=600,compression="lzw")
ggsave("Dunkleosteus Length-Weight.png",dunk_length_weight_graph2,
       device="png",width=165,height=124,units="mm",dpi=300)

If the same allometric effect that affects actinopterygians also affects arthrodires, then the smaller Dunkleosteus may be slightly shorter than reconstructed here (by ~10% in CMNH 7424?). However, just looking at armor dimensions alone there is still a clear deepening effect with size. Note that normally creating a length-weight curve out of N = 4 is not recommended, and is only done here because of the limited number of specimens of D. terrelli for which 3D reconstructions are available that body mass can be estimated.

The resulting allometric exponent suggests that Dunkleosteus became heavier relative to its length throughout ontogeny, which agrees with the observation that there is a general deepening of the thoracic armor throughout the observed ontogenetic series. Note: if it is eventually demonstrated that the positive allometry in OOL characterizes sharks and all fishes more generally, then this might reduce the allometric exponent somewhat. That is, this would make the younger individuals of Dunkleosteus shorter, which then would make them potentially heavier relative to their reduced anteroposterior length estimates (as girth is unchanged). Froese (2006) notes that allometric exponents in excess of 3.5 are very unusual for fishes, and this suggests that while Dunkleosteus does appear to get deeper-bodied throughout ontogeny (as indicated by its dorsoventral trunk proportions relative to measurable anteroposterior dimensions like OOL or thoracic armor length), the allometric exponent estimated here might prove to be a slight overestimate.

16.6 Armor weight of CMNH 6090

Element Volume (cm^3)
Head and thoracic shield 21133.93
Right posteroventrolateral plate 882.10
Left posteroventrolateral plate 711.46
Right anteroventrolateral plate 685.98
Left anteroventrolateral plate 674.54
Posterior median plate 209.61
Anterior median plate 88.96
Total volume 24386.59

Note: the volume estimation of the head and thoracic shield does not subtract the volume of the metal armature, which is located very close to the specimen and cannot be easily separated from the 3D model of the fossil. However, the armature is small and unobtrusive enough that the estimated volume for the head and trunk armor should be close to the actual value, if slightly higher due to the presence of the armature.

data.frame("armor_mass"=c(24386.59*1.2),"density"=(1.20),"source"="whole bone, Wall et al. 1983")%>%
  add_row("armor_mass"=c(24386.59*1.3),"density"=(1.30),"source"="whole bone, ICRP 1995")%>%
  add_row("armor_mass"=c(24386.59*1.236),"density"=(1.236),"source"="whole bone, Shephard 1991")%>%
add_row("armor_mass"=c(24386.59*1.86),"density"=(1.86),"source"="Assuming entire bone is cortical bone, Blanton and Biggs 1968")%>%
  mutate(density=as.character(density))%>%
  add_row("armor_mass"=c(24386.59*0.25*1.86+24386.59*0.75*1.06),"density"=c("1.86 (cortical), 1.06 (cancellous)"),"source"="Assuming 25% of the bone volume is cortical, following Wall et al. 1983, densities from Blanton and Biggs 1968")%>%
  mutate(armor_mass=armor_mass/1000,
         percent_mass=(armor_mass/dunk_weights%>%
                         filter(specimen=="CMNH 6090")%>%pull(body_mass))*100)%>% select(armor_mass,density,percent_mass,source) %>%
  kable(digits=c(2,3,2,1),align=c("c"),
        col.names = c("Mass of Armor (kg)","Density (g/cm3)","Armor Weight as Percent Armor-Free Body Mass","References"),caption="Armor masses of CMNH 6090 (estimated mass without making assumptions for armor using ellipsoid model")%>%
  kable_styling()
Table 16.5: Armor masses of CMNH 6090 (estimated mass without making assumptions for armor using ellipsoid model
Mass of Armor (kg) Density (g/cm3) Armor Weight as Percent Armor-Free Body Mass References
29.26 1.2 7.47 whole bone, Wall et al. 1983
31.70 1.3 8.09 whole bone, ICRP 1995
30.14 1.236 7.70 whole bone, Shephard 1991
45.36 1.86 11.58 Assuming entire bone is cortical bone, Blanton and Biggs 1968
30.73 1.86 (cortical), 1.06 (cancellous) 7.85 Assuming 25% of the bone volume is cortical, following Wall et al. 1983, densities from Blanton and Biggs 1968

16.7 How large would a shark have to be in order to equal the body masses of Dunkleosteus terrelli?

fit.shark_bodymass<-lm(log(total_length)~log(body_mass),
                       data_final%>%filter(clade=="Chondrichthyes",
                                           !order %in% c("Rhinobatiformes","Rajiformes","Chimaeriformes")))
dunk_weights%>%
  filter(taxon=="Dunkleosteus terrelli")%>%
  mutate(body_mass=body_mass*1000)%>%
  augment(fit.shark_bodymass,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(fit.shark_bodymass)$CF),
         diff=.fitted-total_length,
         body_mass=round(body_mass/1000,1))%>%
  select(specimen,total_length,body_mass,.fitted,.lower,.upper,diff)%>%
  kable(digits=1,col.names=c("Specimen","Total Length","Estimated Mass (kg)","Est.","Lower 95% P.I.","Upper 95% P.I.","% Diff."),align="c",
        caption="Size of sharks (in cm) necessary to achieve weights similar to individuals of <i>Dunkleosteus terrelli</i>, showing much greater weight relative to length of latter taxon")%>%
  add_header_above(c(" "=3,"Estimated Total Length of Shark"=3," "=1))%>%
  kable_styling()
Table 16.6: Size of sharks (in cm) necessary to achieve weights similar to individuals of Dunkleosteus terrelli, showing much greater weight relative to length of latter taxon
Estimated Total Length of Shark
Specimen Total Length Estimated Mass (kg) Est. Lower 95% P.I. Upper 95% P.I. % Diff.
CMNH 7424 188.9 106.7 256.3 199.2 329.6 67.3
CMNH 7054 295.5 381.4 375.2 291.5 482.9 79.7
CMNH 6090 283.3 391.7 378.2 293.8 486.7 94.9
CMNH 5768 340.7 1008.4 501.9 389.8 646.4 161.3

Note that although the estimated weight of a shark the size of CMNH 5768 gets very close to the reported total length of the Lausanne Carcharodon (De Maddalena et al. 2003), lamnid sharks in general have a much shorter and heavier body than most sharks do. As this regression equation is based on all sharks (i.e., carcharhinids, hexanchiids, cetorhinids, etc.) CMNH 5768 is still expected to be much smaller than a large lamnid like MZL 23981.

16.8 Weight of the largest known specimen of Dunkleosteus (CMNH 5936)

# Estimated length for CMNH 5936 was calculated as the mean of the estimated lengths using JM3 and JM5 using the model "fit.shape_clade3"

CMNH_5936_dimensions<-data.frame(OOL=mean(c(predict(fit.JM5,data.frame(JM5=33.9)),
                                          predict(fit.JM3,data.frame(JM3=18.63)))),
           clade="Placodermi",shape="fusiform",habitat="pelagic",swimbladder=F,heterocercal=T,
           family="Dunkleosteidae",
           length5768=exp(predict(fit.shape_clade3,fossil_taxa%>%filter(specimen=="CMNH 5768")))*
             regression.stats(fit.shape_clade3)$CF)%>%
  mutate(total_length=exp(predict(fit.shape_clade3,.))*regression.stats(fit.shape_clade3)$CF,
         girth=total_length*(fossil_taxa%>%filter(specimen=="CMNH 5768")%>%pull(girth)/
                               length5768),
         head_length=total_length*(fossil_taxa%>%filter(specimen=="CMNH 5768")%>%pull(head_length)/
                               length5768))%>%
  mutate(precaudal_length=exp(predict(total_length_to_standard_length,.))*
           regression.stats(total_length_to_standard_length)$CF,
         fork_length=exp(predict(total_length_to_fork_length,.))*
           regression.stats(total_length_to_fork_length)$CF)

rbind(predict(fit.ellipsoid,CMNH_5936_dimensions,interval="predict")%>%exp(),
      predict(fit.ellipsoid_pelagic,CMNH_5936_dimensions,interval="predict")%>%exp())%>%
  data.frame()%>%
  add_row(fit=exp(predict(dunk_length_weight,CMNH_5936_dimensions))*
            regression.stats(dunk_length_weight)$CF,
          lwr=exp(predict(dunk_length_weight,CMNH_5936_dimensions,interval="prediction")[2])*
            regression.stats(dunk_length_weight)$CF,
          upr=exp(predict(dunk_length_weight,CMNH_5936_dimensions,interval="prediction")[3])*
            regression.stats(dunk_length_weight)$CF)%>%
  mutate(across(everything(),~./1000))%>%
  add_row(fit=
      CMNH_5936_dimensions%>%
      mutate(weight_carcharodon=45.98*((girth/100)^2*(precaudal_length/100))^0.9267)%>%
               pull(weight_carcharodon))%>%
  rownames_to_column()%>%
  add_column(model=c("Ellipsoid Model, All Fishes","Ellipsoid Model, Pelagic Fishes","Dunkleosteus Length-Weight Model","Carcharodon Length-Weight Model"))%>%
  mutate(range=ifelse(is.na(lwr),NA,paste0("(",round(lwr,1),"–",round(upr,1),")")))%>%
  column_to_rownames("model")%>%select(-rowname,-lwr,-upr)%>%
  kable(digits=1,col.names=c("Est.","95% P.I."),align="c",
        caption="Weight estimates of the largest known specimen of <i>Dunkleosteus terrelli</i> (CMNH 5936), assuming missing proportions were similar to CMNH 5768. All body masses in kg.")%>%
  kable_styling()
Table 16.7: Weight estimates of the largest known specimen of Dunkleosteus terrelli (CMNH 5936), assuming missing proportions were similar to CMNH 5768. All body masses in kg.
Est. 95% P.I.
Ellipsoid Model, All Fishes 1763.9 (982.2–3168)
Ellipsoid Model, Pelagic Fishes 1731.6 (1175.9–2549.8)
Dunkleosteus Length-Weight Model 1495.7 (331.3–6752.1)
Carcharodon Length-Weight Model 1494.2 NA

17 Miscellaneous analyses

17.1 Calculating the length of CMNH 5768 using “entering angle of the body”

One of the few previous methods to explicitly estimate total length in Dunkleosteus using statistical methods other than scaling off of Coccosteus was that of Hussakof (1906), who estimated total length in a juvenile Dunkleosteus terrelli (= “Dinichthys intermedius” of this author) using “entering angle of the body”. This measurement represents the anteroposterior length of the animal from the anteriormost point on the body to the location of greatest body depth, following Dean (1902) and Parsons (1888). The entering angle of the body ends “with wonderful uniformity” (Parsons, 1888) at approximately 36% the total length of the animal, and this appears to be consistent across nektonic fishes and marine tetrapods (= cetaceans), suggesting a biomechanical reason for its existence.

Assuming the apex of the mediodorsal represents the tallest point on the body in Dunkleosteus terrelli, the “entering angle of the body” in CMNH 5768 is estimated to measure 125–128 cm (depending on whether this is measured to the anterior end of the skull roof or the protruding supragnathals). Assuming the “entering angle” represents 36% of total body length the estimated total length for CMNH 5768 is 347–355 cm, very similar to the total lengths calculated via OOL. This increases the confidence that OOL accurately predicts total length in D. terrelli.

17.2 With only specimens measured directly

Because so much of the data comes from the previously published literature, there is a concern that errors in data reporting might bias results. Thus, an analysis was performed using only specimens measured by the author either directly or from published photographs in the literature.

fit.engelman_measured<-lm(log(total_length)~log(OOL),
                 data=data_final%>%
  filter(`Method of Measurement` %in% (c("measured from figure/photo",
                                         "measured from photo",
                                         "measured from specimen",
                                         "mixed"))))
rbind("All Specimens"=regression.stats(fit.OOL),
      "Specimens Measured Directly"=regression.stats(fit.engelman_measured))

Error rates of the two models are about the same, and this is compounded by the fact that many of the taxa that could only be taken from literature data includes some species with unusual body proportions, which could inflate the error of the model including all specimens examined.

17.3 Estimating body size in Dunkleosteus terrelli using (clade-corrected) mouth dimensions

Ferrón et al. (2017) estimated body size in Dunkleosteus terrelli using mouth dimensions in sharks. However, these method do not appear to reliably predict body size in arthrodires, due to arthrodires having proportionally larger mouths than sharks (Engelman, in press). However, running a regression model using mouth width data for arthrodires and sharks from Engelman (in press) and adding an additional coefficient to control for differences in mouth proportions between arthrodires and sharks produces the following results…

fit.mouthwidth.clade_corrected<-
  lm(log(total_length)~log(mouth_width)+clade,
     data=data_final%>%filter(clade %in% c("Placodermi","Chondrichthyes"),
                              !order %in% c("Chimaeriformes","Rajiformes"),
                              !genus %in% c("Alopias"))%>%
       filter(length_as=="total length"))
summary(fit.mouthwidth.clade_corrected)
## 
## Call:
## lm(formula = log(total_length) ~ log(mouth_width) + clade, data = data_final %>% 
##     filter(clade %in% c("Placodermi", "Chondrichthyes"), !order %in% 
##         c("Chimaeriformes", "Rajiformes"), !genus %in% c("Alopias")) %>% 
##     filter(length_as == "total length"))
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.90723 -0.13517  0.01429  0.14832  0.59746 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       2.93008    0.02828 103.616   <2e-16 ***
## log(mouth_width)  0.81183    0.01312  61.887   <2e-16 ***
## cladePlacodermi  -0.74742    0.07861  -9.508   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2203 on 442 degrees of freedom
##   (112 observations deleted due to missingness)
## Multiple R-squared:  0.8989, Adjusted R-squared:  0.8984 
## F-statistic:  1964 on 2 and 442 DF,  p-value: < 2.2e-16

Based on this, arthrodires and sharks show significantly different mouth sizes, and including the additional term has a significant effect on the resulting predictions.

fossil_taxa%>%
  drop_na(mouth_width)%>%
  filter(!!fossil.specimens)%>%
  mutate(total_length=ifelse(genus=="Dunkleosteus",NA,total_length))%>%
  augment(fit.mouthwidth.clade_corrected,newdata=.,
          interval="prediction")%>%
  mutate(across(.fitted:.upper,~exp(.)*
                  regression.stats(fit.mouthwidth.clade_corrected)$CF),
         range=paste0("(",round(.lower,1),"–",round(.upper,1),")"),
         PE=paste0("(",
                   round(.fitted*(1-regression.stats(fit.mouthwidth.clade_corrected)$adjPE/100),
                         1),
                   "–",
                   round(.fitted*(1+regression.stats(fit.mouthwidth.clade_corrected)$adjPE/100),
                         1),
                   ")"
                   ))%>%
  rename(fitted_mouthwidth=.fitted)%>%
  augment(fit.shape_clade3,newdata=.)%>%
  rename(fitted_OOL=.fitted)%>%
  mutate(fitted_OOL=exp(fitted_OOL)*regression.stats(fit.shape_clade3)$CF)%>%
  select(taxon, specimen, total_length, fitted_OOL, fitted_mouthwidth, PE, range)%>%
  kable(digits=1,align=c("l","c","c","c","c","c","c"),
        col.names = c("Taxon","Specimen","Actual Length","Estimated Length using OOL","Est.","+/- %PE","95% P.I."),
        caption="Lengh estimates for <i>Dunkleosteus terrelli</i> and other arthrodires using mouth width and a combined dataset of sharks and arthrodires, with an additional coefficient for clade membership")%>%
  add_header_above(c(" "=4,"Estimating using Clade-Corrected Mouth Width"=3))%>%
  column_spec(1, italic = T)%>%
  column_spec(5, bold = T)%>%
  kable_styling()
Table 17.1: Lengh estimates for Dunkleosteus terrelli and other arthrodires using mouth width and a combined dataset of sharks and arthrodires, with an additional coefficient for clade membership
Estimating using Clade-Corrected Mouth Width
Taxon Specimen Actual Length Estimated Length using OOL Est. +/- %PE 95% P.I.
Millerosteus minor Composite Millerosteus 14.9 15.6 15.2 (12.6–17.7) (9.6–24)
Incisoscutum ritchei Recon. (Trinajstic 2013) 30.3 30.8 31.0 (25.7–36.2) (19.6–49)
Coccosteus cuspidatus Recon. (M & W 1968) 39.4 42.7 43.3 (36–50.6) (27.3–68.5)
Plourdosteus canadensis MNHM 2-177 37.5 49.9 50.3 (41.8–58.8) (31.8–79.6)
Holonema westolli Recon. (Miles 1971) 60.6 49.7 65.2 (54.2–76.2) (41.2–103.2)
Watsonosteus fletti NMS G.1995.4.2 56.6 63.4 70.1 (58.3–82) (44.3–111)
Gen. et sp. nov. CMNH 50233 63.0 66.5 51.2 (42.6–59.9) (32.4–81.1)
Amazichthys trinajsticae AA.MEM.DS.8 89.7 75.7 63.6 (52.9–74.3) (40.2–100.7)
Dunkleosteus terrelli CMNH 7424 NA 188.9 143.1 (118.9–167.2) (90.3–226.8)
Dunkleosteus terrelli CMNH 6090 NA 283.3 221.6 (184.2–259) (139.6–351.7)
Dunkleosteus terrelli CMNH 7054 NA 295.5 225.4 (187.4–263.4) (142–357.9)
Dunkleosteus terrelli CMNH 5768 NA 340.7 290.1 (241.2–339) (182.6–461)

Thus, when corrected for differences in mouth proportions between arthrodires and sharks, mouth width produces estimates of body size for Dunkleosteus terrelli that are close to estimates under OOL, though slightly lower.

data_final%>%
  filter(clade %in% c("Chondrichthyes","Placodermi"),
         !order %in% c("Chimaeriformes","Rajiformes"),
         genus!="Alopias")%>%
  ggplot(aes(total_length,mouth_width))+
  geom_star(aes(starshape=clade,fill=clade),
            data=.%>%filter(genus!="Dunkleosteus"))+
  scale_x_continuous(trans="log10")+
  scale_y_continuous(trans="log10")+
  geom_smooth(data=.%>%
                mutate(clade="Chondrichthyes")%>%
                augment(fit.mouthwidth.clade_corrected,newdata=.)%>%
                mutate(.fitted=exp(.fitted)*regression.stats(fit.mouthwidth.clade_corrected)$CF),
              aes(x=.fitted,y=mouth_width,color=clade),formula=y~x,method="lm",se=F)+
  geom_smooth(data=.%>%
                mutate(clade="Placodermi",!!fossil.specimens)%>%
                augment(fit.mouthwidth.clade_corrected,newdata=.)%>%
                mutate(.fitted=exp(.fitted)*regression.stats(fit.mouthwidth.clade_corrected)$CF),
              aes(x=.fitted,y=mouth_width,color=clade),formula=y~x,method="lm",se=F)+
  geom_vline(xintercept=300,linetype="dashed")+
  geom_star(aes(x=total_length,y=mouth_width,starshape=clade,fill=clade),
            data=fossil_taxa %>% filter(genus=="Amazichthys"))+
  geom_star(aes(x=total_length,y=mouth_width,starshape=clade,fill=clade),
            data=fossil_taxa %>% filter(clade=="Placodermi",!!fossil.specimens) %>%
              drop_na(total_length) %>%
              filter(genus!="Dunkleosteus"),
            size=2,show.legend=F)+
  geom_star(aes(x=.fitted,y=mouth_width,starshape=clade),
            data=fossil_taxa %>% filter(genus=="Dunkleosteus",!!fossil.specimens) %>%
              augment(fit.mouthwidth.clade_corrected,newdata=.)%>%
              mutate(.fitted=exp(.fitted)*
                       regression.stats(fit.mouthwidth.clade_corrected)$CF),
            color="black",fill="white",size=3,show.legend=F)+
  labs(x="Total Length (cm)",y="Mouth Width (cm)",
       color="Clade",starshape="Clade",fill="Clade")+
  scale_starshape_manual(values=c(15,1))+
  annotate(geom="text",x=290,y=1.5,hjust=1,label="3 m")+
  theme(legend.position=c(0.85,0.2))
Estimated length of Dunkleosteus terrelli (in white) using mouth width making corrections for the wider mouths of arthrodires.

Figure 17.1: Estimated length of Dunkleosteus terrelli (in white) using mouth width making corrections for the wider mouths of arthrodires.

17.4 Estimating length in Dunkleosteus using arthrodires only

Because of the wide variety of fishes examined in this study, it is worth examining arthrodires known from whole-body fossils alone to see if using only these taxa to estimate length in Dunkleosteus produces similar results.

placoderms_only_OOL<-lm(log(total_length)~log(OOL),
                        data_final%>%filter(!!fossil.specimens,genus!="Dunkleosteus"))
placoderms_only_headlength<-lm(log(total_length)~log(head_length),
                        data_final%>%filter(!!fossil.specimens,genus!="Dunkleosteus"))

rbind("Using Head Length"=regression.stats(placoderms_only_headlength),
      "Using OOL"=regression.stats(placoderms_only_OOL))
fossil_taxa%>%
  filter(!!fossil.specimens)%>%
  mutate(total_length=ifelse(length_as=="estimated t.l.",NA,total_length))%>%
  augment(placoderms_only_headlength,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(placoderms_only_headlength)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit1",.)))%>%
  augment(placoderms_only_OOL,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~exp(.)*regression.stats(placoderms_only_OOL)$CF))%>%
  rename_at(vars(starts_with('.')), funs(paste0("fit2",.)))%>%
  mutate(range1=paste0("(",round(fit1.lower,1),"-",round(fit1.upper,1),")"),
         range2=paste0("(",round(fit2.lower,1),"-",round(fit2.upper,1),")"),
         PE1_lower=fit1.fitted*(1-(regression.stats(placoderms_only_headlength)$adjPE)/100),
         PE1_upper=fit1.fitted*(1+(regression.stats(placoderms_only_headlength)$adjPE)/100),
         PE2_lower=fit2.fitted*(1-(regression.stats(placoderms_only_OOL)$adjPE)/100),
         PE2_upper=fit2.fitted*(1+(regression.stats(placoderms_only_OOL)$adjPE)/100),
         PE_range1=paste0("(",round(PE1_lower,1),"-",round(PE1_upper,1),")"),
         PE_range2=paste0("(",round(PE2_lower,1),"-",round(PE2_upper,1),")"),
         PE=((fit1.fitted-total_length)/fit1.fitted),
         PE2=((fit2.fitted-total_length)/fit2.fitted))%>%
  select(taxon,specimen,total_length,
         fit1.fitted,PE_range1,range1,PE,
         fit2.fitted,PE_range2,range2,PE2)%>%
  kable(digits=c(1,1,1,1,1,1,3,1,1,1,3),
        col.names=c("Taxon","Specimen","Actual","Est.","+/- PE","95% P.I.","%PE",
                    "Est.","+/- PE","95% C.I.","%PE"),
        align=c("l","c","c","c","c","c","c","c","c","c","c"),
        caption="Length estimates for <i>Dunkleosteus terrelli</i> and complete arthrodires using only data from arthrodire specimens with whole body remains. All measurements in cm.")%>%
  column_spec(1, italic = T)%>%
  column_spec(c(4,8), bold = T)%>%
  add_header_above(c(" "=3,"Using Head Length"=4,"Using OOL"=4))%>%
  kable_styling()
Table 17.2: Length estimates for Dunkleosteus terrelli and complete arthrodires using only data from arthrodire specimens with whole body remains. All measurements in cm.
Using Head Length
Using OOL
Taxon Specimen Actual Est. +/- PE 95% P.I. %PE Est. +/- PE 95% C.I. %PE
Millerosteus minor FMNH PF 1089 13.7 12.5 (11.3-13.7) (9.1-17.1) -0.098 13.3 (12-14.6) (9.5-18.6) -0.034
Millerosteus minor Composite Millerosteus 14.9 15.7 (14.2-17.2) (11.6-21.2) 0.048 15.3 (13.8-16.7) (11-21.2) 0.020
Africanaspis dorissa Gess and Trinajstic 2017 23.0 24.0 (21.8-26.2) (18.2-31.8) 0.042 22.9 (20.6-25.1) (16.8-31.1) -0.006
Incisoscutum ritchei Recon. (Trinajstic 2013) 30.3 29.9 (27.1-32.7) (22.7-39.3) -0.013 29.2 (26.4-32.1) (21.7-39.5) -0.035
Coccosteus cuspidatus NMS 1893.107.27 29.6 32.4 (29.4-35.4) (24.7-42.6) 0.086 32.3 (29.2-35.4) (24-43.6) 0.083
Coccosteus cuspidatus NMS 1897.55.6 32.3 33.6 (30.5-36.7) (25.6-44.1) 0.039 33.2 (30-36.3) (24.6-44.7) 0.027
Coccosteus cuspidatus FMNH PF 1673 37.1 35.0 (31.7-38.2) (26.6-46) -0.061 33.6 (30.3-36.8) (24.9-45.2) -0.106
Coccosteus cuspidatus Recon. (M & W 1968) 39.4 36.6 (33.2-40.1) (27.9-48.1) -0.075 40.1 (36.2-43.9) (29.7-54) 0.017
Coccosteus cuspidatus NMS 1900.12.12 34.4 36.8 (33.4-40.3) (28.1-48.4) 0.067 39.4 (35.6-43.2) (29.2-53) 0.127
Coccosteus cuspidatus ROM VP 52664 37.5 39.9 (36.2-43.6) (30.3-52.4) 0.059 38.8 (35.1-42.6) (28.8-52.3) 0.034
Plourdosteus canadensis MNHM 2-177 37.5 44.8 (40.7-49) (34.1-59) 0.163 46.6 (42.1-51.1) (34.5-62.9) 0.195
Dickosteus threiplandi NMS 1987.7.118 43.7 47.4 (43-51.8) (36-62.4) 0.078 50.7 (45.8-55.6) (37.5-68.5) 0.138
Holonema westolli Recon. (Miles 1971) 60.6 49.5 (44.9-54.1) (37.6-65.2) -0.226 46.4 (41.9-50.9) (34.4-62.6) -0.307
Watsonosteus fletti NMS G.1995.4.2 56.6 58.8 (53.3-64.2) (44.4-77.8) 0.037 58.6 (53-64.2) (43.2-79.6) 0.034
Gen. et sp. nov. CMNH 50233 63.0 63.0 (57.2-68.9) (47.5-83.6) 0.000 61.3 (55.4-67.3) (45.1-83.4) -0.027
Dickosteus threiplandi NHMUK PV P 49663 52.3 64.4 (58.4-70.4) (48.5-85.5) 0.188 62.2 (56.2-68.2) (45.7-84.6) 0.159
Amazichthys trinajsticae AA.MEM.DS.8 89.7 69.0 (62.6-75.4) (51.8-91.9) -0.300 69.5 (62.8-76.2) (50.8-95.1) -0.291
Dunkleosteus terrelli CMNH 7424 NA 168.4 (152.8-184.1) (119.2-237.9) NA 167.4 (151.3-183.6) (114.8-244.3) NA
Dunkleosteus terrelli CMNH 6090 NA 249.8 (226.6-273) (170.5-366) NA 247.1 (223.3-271) (162.8-375.1) NA
Dunkleosteus terrelli CMNH 7054 NA 262.2 (237.9-286.6) (178.1-386.1) NA 257.4 (232.6-282.2) (168.8-392.5) NA
Dunkleosteus terrelli CMNH 5768 NA 298.7 (271-326.5) (200.2-445.6) NA 295.1 (266.7-323.5) (190.6-456.8) NA

Head length and OOL are both very accurate in predicting length in complete arthrodires (mostly coccosteomorphs). However, they result in dramatic underestimates of total length in Amazichthys trinajsticae, likely because of the very small head and signs of an elongate body plan in this taxon (see below and manuscript).

The estimated lengths for Dunkleosteus under these models is similar to the lengths predicted using the all-fish model, specifically the model using head length by itself. However, as mentioned in the manuscript, lengths of less than 3 m are unlikely for CMNH 5768 because this would require a body plan with next to no caudal fin or caudal peduncle. It is likely the head size of the Achannaras coccosteomorphs is slightly above average based on their proportions (specifically, the location of the cranio-thoracic joint), whereas Amazichthys’ head size is below average, especially for arthrodires.

Additionally, it is highly likely the model is being influenced by extrapolation error, as it must be extrapolated roughly five times the span of the data in order to predict body size in Dunkleosteus. It is for these reasons that the model considering only arthrodires is not favored here.

Additionally, despite having a percent error of only about 8%, the 95% prediction intervals for the model are very large, almost as large as the model fit using all fishes. This is, as stated in the manuscript, in part due to the back-transformation of errors from a logarithmic scale.

Also note that the length estimates here are not the same as the estimates cited as “Simple scaling from Coccosteus, head length” in the manuscript. That is calculated based on the proportions of the reconstruction of Coccosteus cuspidatus in Miles and Westoll (1968), rather than a regression equation.

17.4.1 Scaling of head size in Arthrodira

fossil_taxa %>%
  filter(length_as=="total length") %>%
  mutate(taxon=ifelse(genus=="Newspecies","CMNH aspinothoracid",taxon)) %>%
  mutate(clade2=case_when(genus %in% c("Amazichthys","Newspecies")~"Aspinothoracida",
                          genus %in% c("Millerosteus","Dickosteus",
                                       "Watsonosteus","Coccosteus",
                                       "Incisoscutum","Plourdosteus")~"Coccosteomorpha",
                          genus %in% c("Africanaspis","Holonema")~"Basal Arthrodira",)) %>%
  augment(lm(total_length~head_length,.),newdata=.)%>%
  ggplot(aes(head_length,total_length))+
  geom_smooth(method="lm",formula=y~x,alpha=0.2)+
  geom_star(starshape=15,aes(fill=clade2))+
  geom_text(aes(label=taxon,
                y=total_length+
                  ifelse(.resid<0|genus %in% c("Amazichthys","Millerosteus"),-2,3)+
                  ifelse(genus %in% c("Millerosteus"),-1,0)+
                  ifelse(genus %in% c("Africanaspis"),1,0),
                fontface=ifelse(genus=="Newspecies",1,3),
                hjust=ifelse((.resid>0|genus=="Amazichthys")&genus!="Millerosteus",1,
                             ifelse(specimen %in% c("CMNH 50233"),0.25,
                                    ifelse(specimen %in% c("NHMUK PV P 49663"),0.5,0)))),
            size=4)+
  labs(fill="Arthrodire Clade",x="Head Length (cm)",y="Total Length (cm)")+
  theme(legend.position=c(0.85,0.2))
Plot of head size versus total length on a (non log-transformed) dataset of all arthrodires with complete remains

Figure 17.2: Plot of head size versus total length on a (non log-transformed) dataset of all arthrodires with complete remains

17.5 Residuals of Amazichthys trinajsticae

OOL_residuals%>%
  mutate(shape=factor(shape,levels=c("macruriform","anguilliform","elongate","fusiform","compressiform")))%>%
  filter(ontogeny %in% c("adult","subadult")|is.na(ontogeny))%>%
  group_by(taxon)%>%
  summarise(residuals=mean(residuals),clade=unique(clade),genus=unique(genus),shape=unique(shape))%>%
  filter(genus %in% c("Amazichthys","Kajikia","Istiompax","Esox","Loxodon","Chiloscyllium","Sphyraena","Scomberoides","Thyrsitoides","Albula"))%>%
  select(!genus)%>%
  mutate(taxon=str_replace(taxon,"_"," "))%>%
  arrange(desc(clade))%>%
  kable(digits=3,align=c("l","c","c","c"),
        col.names=c("Taxon","Residuals","Clade","Body Shape"),
        caption="Residuals of <i>Amazichthys trinajsticae</i> compared to similar fishes, showing how <i>A. trinajsticae</i> best compares to elongate or 'semi-elongate' fishes that have some degree of axial elongation")%>%
  column_spec(1, italic = T)%>%
  row_spec(1, bold = T)%>%
  kable_styling()
Table 17.3: Residuals of Amazichthys trinajsticae compared to similar fishes, showing how A. trinajsticae best compares to elongate or ‘semi-elongate’ fishes that have some degree of axial elongation
Taxon Residuals Clade Body Shape
Amazichthys trinajsticae 0.158 Placodermi elongate
Chiloscyllium griseum 0.172 Chondrichthyes elongate
Chiloscyllium punctatum 0.180 Chondrichthyes elongate
Loxodon macrorhinus 0.159 Chondrichthyes fusiform
Albula argentea 0.145 Actinopterygii fusiform
Albula glossodonta 0.126 Actinopterygii fusiform
Albula vulpes 0.130 Actinopterygii fusiform
Esox aquitanicus -0.006 Actinopterygii fusiform
Esox lucius 0.107 Actinopterygii elongate
Esox masquinongy 0.137 Actinopterygii elongate
Esox niger 0.153 Actinopterygii elongate
Istiompax indica 0.156 Actinopterygii fusiform
Kajikia albida 0.265 Actinopterygii elongate
Kajikia audax 0.169 Actinopterygii elongate
Scomberoides commersonnianus 0.120 Actinopterygii fusiform
Scomberoides lysan 0.268 Actinopterygii fusiform
Scomberoides tala 0.331 Actinopterygii fusiform
Scomberoides tol 0.260 Actinopterygii fusiform
Sphyraena acutipinnis 0.083 Actinopterygii elongate
Sphyraena afra 0.139 Actinopterygii elongate
Sphyraena africana 0.032 Actinopterygii elongate
Sphyraena arabiansis 0.108 Actinopterygii elongate
Sphyraena barracuda 0.129 Actinopterygii elongate
Sphyraena chrysotaenia -0.026 Actinopterygii elongate
Sphyraena flavicauda 0.062 Actinopterygii elongate
Sphyraena guaguancho -0.003 Actinopterygii elongate
Sphyraena idiastes 0.037 Actinopterygii elongate
Sphyraena jello 0.114 Actinopterygii elongate
Sphyraena obtustata -0.073 Actinopterygii elongate
Sphyraena putnamae 0.129 Actinopterygii elongate
Sphyraena qenie 0.105 Actinopterygii elongate
Sphyraena toxeuma 0.069 Actinopterygii elongate
Sphyraena viridensis 0.086 Actinopterygii elongate
Thyrsitoides marleyi 0.219 Actinopterygii elongate

Based on this, Amazichthys best compares to taxa that show a slight degree of axial elongation, such as Esox spp., larger species of Sphyraena spp., Istiompax indica, Kajikia audax (but not K. albida), and Scomberoides commersonnianus.

17.6 Relative pelvis position across non-acanthopterygian fishes

17.6.1 Using individual observations

pelvic_lengths_allspecimens<-data_final%>%
  mutate(percent_prepelvic_length=prepelvic_length/total_length,
         pectoral_pelvic_length=prepelvic_length-prepectoral_length,
         pectoral_precaudal_length=precaudal_length-prepectoral_length,
         higher_group=factor(higher_group,levels=c("Petromyzontiformes","Arthrodira","Chondrichthyes","Sarcopterygii","Basal Actinopterygii","Basal Teleostei","Ostariophysi","Stem Euteleostei","Acanthopterygii")),
         order2=ifelse(order %in% c("Carcharhiniformes","Squaliformes"),
                       family,order))%>%
  select(taxon,specimen,references,percent_prepelvic_length,pectoral_pelvic_length,
         pectoral_precaudal_length,everything())%>%
  arrange(higher_group,order,taxon)%>%
  filter(higher_group!="Acanthopterygii")
level_info_2 <- pelvic_lengths_allspecimens %>%
  arrange(higher_group, order2) %>% 
  pull(order2) %>% 
  unique()
pelvic_lengths_allspecimens<-pelvic_lengths_allspecimens%>%
  mutate(order2=factor(order2,levels=rev(level_info_2)))%>%
  drop_na(prepelvic_length,total_length,precaudal_length,prepectoral_length)
grid.arrange(
  ggplot(pelvic_lengths_allspecimens,aes(percent_prepelvic_length,order2))+
    geom_vline(aes(xintercept=mean(percent_prepelvic_length)),linetype="dashed")+
    geom_violin(scale="width",aes(fill=higher_group))+
    geom_boxplot()+
    ggtitle("A")+
    coord_cartesian(xlim=c(0,1))+
    labs(x="Prepelvic Length as Percent of Total Length",y="Order")+
    theme(legend.position="none"),
  ggplot(pelvic_lengths_allspecimens%>%
           drop_na(precaudal_length,prepectoral_length),
         aes(pectoral_pelvic_length/pectoral_precaudal_length,order2))+
        geom_vline(aes(xintercept=mean(pectoral_pelvic_length/pectoral_precaudal_length),group=higher_group),
                   linetype="dashed")+
    geom_violin(scale="width",aes(fill=higher_group))+
    geom_boxplot()+
    ggtitle("B")+
    coord_cartesian(xlim=c(0,1))+
    labs(x="Prepectoral-Prepelvic Length as Percent of Prepectoral-Precaudal Length",y="Order")+
    theme(legend.position="none"))
(A) Prepelvic-length as a percentage of total length in non-acanthopterygian fishes, showing how this value is about 45% of total length. (B) the length from the origin of the pectoral and pelvic fins as a percentage of the length from the origin of the pectoral fin to the base of the caudal peduncle in non-acanthopterygian fishes, showing how the pelvic fin is positioned about midway between the origin of the pectoral fin to the caudal peduncle, especially in sharks and arthrodires. These graphs calculated using all specimens.

Figure 17.3: (A) Prepelvic-length as a percentage of total length in non-acanthopterygian fishes, showing how this value is about 45% of total length. (B) the length from the origin of the pectoral and pelvic fins as a percentage of the length from the origin of the pectoral fin to the base of the caudal peduncle in non-acanthopterygian fishes, showing how the pelvic fin is positioned about midway between the origin of the pectoral fin to the caudal peduncle, especially in sharks and arthrodires. These graphs calculated using all specimens.

17.6.2 Using species averages

pelvic_lengths_speciesaverages<-data_final %>%
  mutate(order2=ifelse(order %in% c("Carcharhiniformes","Squaliformes"),family,order)) %>%
  drop_na(prepelvic_length,precaudal_length,prepectoral_length,total_length) %>%
  group_by(taxon) %>%
  summarise(across(c(prepelvic_length,prepectoral_length,precaudal_length,total_length),mean),
            across(c(clade,order2,higher_group),unique)) %>%
  mutate(percent_prepelvic_length=prepelvic_length/total_length,
         pectoral_pelvic_length=prepelvic_length-prepectoral_length,
         pectoral_precaudal_length=precaudal_length-prepectoral_length,
         higher_group=factor(higher_group,levels=c("Petromyzontiformes","Arthrodira","Chondrichthyes","Sarcopterygii","Basal Actinopterygii","Basal Teleostei","Ostariophysi","Stem Euteleostei","Acanthopterygii"))) %>%
  arrange(higher_group,order2,taxon) %>%
  filter(higher_group!="Acanthopterygii") %>%
  mutate(order2=factor(order2,levels=rev(level_info_2)))
grid.arrange(
  ggplot(pelvic_lengths_speciesaverages,aes(percent_prepelvic_length,order2))+
    geom_vline(aes(xintercept=mean(percent_prepelvic_length)),linetype="dashed")+
    geom_violin(scale="width",aes(fill=higher_group))+
    geom_boxplot()+
    coord_cartesian(xlim=c(0,1))+
    labs(x="Prepelvic Length as Percent of Total Length",y="Order")+
    theme(legend.position="none"),
  ggplot(pelvic_lengths_speciesaverages%>%
           drop_na(precaudal_length,prepectoral_length),
         aes(pectoral_pelvic_length/pectoral_precaudal_length,order2))+
    geom_vline(aes(xintercept=mean(pectoral_pelvic_length/pectoral_precaudal_length),
                   group=higher_group),
                   linetype="dashed")+
    geom_violin(scale="width",aes(fill=higher_group))+
    geom_boxplot()+
    coord_cartesian(xlim=c(0,1))+
    labs(x="Prepectoral-Prepelvic Length as Percent of Prepectoral-Precaudal Length",y="Order")+
    theme(legend.position="none"))
(A) Prepelvic-length as a percentage of total length in non-acanthopterygian fishes, showing how this value is about 45% of total length. (B) the length from the origin of the pectoral and pelvic fins as a percentage of the length from the origin of the pectoral fin to the base of the caudal peduncle in non-acanthopterygian fishes, showing how the pelvic fin is positioned about midway between the origin of the pectoral fin to the caudal peduncle, especially in sharks and arthrodires. These graphs calculated using species averages.

Figure 17.4: (A) Prepelvic-length as a percentage of total length in non-acanthopterygian fishes, showing how this value is about 45% of total length. (B) the length from the origin of the pectoral and pelvic fins as a percentage of the length from the origin of the pectoral fin to the base of the caudal peduncle in non-acanthopterygian fishes, showing how the pelvic fin is positioned about midway between the origin of the pectoral fin to the caudal peduncle, especially in sharks and arthrodires. These graphs calculated using species averages.

Based on this, it is clear that the pelvic girdle is typically located about midway along the animal’s body in most non-acanthopterygian fishes, particularly those with unspecialized body shapes. These include Carcharhiniformes, Hexanchiformes, some Squaliformes (i.e., Squalidae), Orectolobiformes, Actinistia, Amiiformes, Elopiformes, etc. This also appears to be the case for arthrodires which are known from complete remains (i.e., Millerosteus, Coccosteus, Watsonosteus, Incisoscutum, Amazichthys, etc.)

Additionally, the pelvic girdle tends to be midway between the origin of the pectoral girdle and the base of the tail fin in most non-acanthopterygian fishes, though the groups more closely related to Acanthopterygii tend to show more acanthopterygian-like proportions. Again, this includes arthrodires for which complete body fossil are known. Given that the pelvic girdle is usually just posterior to the ventral end of the armor in arthrodires, a predicted value of about ~155-160 cm in CMNH 5768, this further suggests lengths of about 3.6 m for typical adult individuals of Dunkleosteus terrelli

17.7 Length of Titanichthys

fit.filterfeeder<-lm(log(total_length)~log(OOL),
                    data_initial%>%filter(genus %in% c("Megachasma","Cetorhinus","Rhincodon")))
# Juvenile specimens for these genera were included as otherwise there were no specimens smaller than ~400 cm total length

fossil_taxa%>%
  filter(genus=="Titanichthys")%>%
  augment(fit.shape_clade3,newdata=.,interval="prediction")%>%
  mutate(across(c(.fitted:.upper),~exp(.)*regression.stats(fit.shape_clade3)$CF)) %>%
  rename_at(vars(starts_with('.')), funs(paste0("fit1",.)))%>%
  augment(fit.filterfeeder,newdata=.,interval="prediction")%>%
  mutate(across(c(.fitted:.upper),~exp(.)*regression.stats(fit.filterfeeder)$CF)) %>%
  rename_at(vars(starts_with('.')), funs(paste0("fit2",.)))%>%
  mutate(PE_1=paste0("(",round(fit1.fitted*(1-(regression.stats(fit.shape_clade3)$adjPE/100)),1),
                     "–",round(fit1.fitted*(1+(regression.stats(fit.shape_clade3)$adjPE/100)),1),")"),
         range_1=paste0("(",round(fit1.lower,1),"–",round(fit1.upper,1),")"),
         PE_2=paste0("(",round(fit2.fitted*(1-(regression.stats(fit.filterfeeder)$adjPE/100)),1),
                     "–",round(fit2.fitted*(1+(regression.stats(fit.filterfeeder)$adjPE/100)),1),")"),
         range_2=paste0("(",round(fit2.lower,1),"–",round(fit2.upper,1),")"))%>%
  select(specimen,OOL,fit1.fitted,PE_1,range_1,fit2.fitted,PE_2,range_2) %>%
  kable(digits=1,align=c("l","c","c","c","c"),
        col.names=c("Taxon","Specimen","Est.","+/-PE","95% P.I.","Est.","+/-PE","95% P.I."),
        caption="Length estimates for <i>Titanichthys clarki</i> using the model with additional coefficients for shape and variable slopes between chondrichthyans and non-chondrichthyans (fit.shape_clade3)")%>%
  add_header_above(c(" "=2,"All Taxa"=3,"Filter-Feeding sharks Only"=3))%>%
  column_spec(c(3,6), bold = T)%>%
  kable_styling()
Table 17.4: Length estimates for Titanichthys clarki using the model with additional coefficients for shape and variable slopes between chondrichthyans and non-chondrichthyans (fit.shape_clade3)
All Taxa
Filter-Feeding sharks Only
Taxon Specimen Est. +/-PE 95% P.I. Est. +/-PE 95% P.I.
CMNH 50319 57.2 371.3 (325.3–417.3) (267.1–516.2) 296.7 (277.1–316.2) (239.6–367.4)
AMNH 7134 64.0 414.8 (363.3–466.2) (298.3–576.6) 334.4 (312.4–356.5) (270.7–413.1)

AMNH FF 7134 is the very large individual of Titanichthys which, at the time of this writing, is mounted in the American Museum of Natural History’s Hall of Vertebrate Origins. CMNH 50319 is the associated subadult individual described by Boyle and Ryan (2017). Remains of Titanichthys tend to be fragmentary and much less common than those of Dunkleosteus, often fragments only assigned to Titanichthys based on armor thinness and texture (Boyle and Ryan 2017, D. Chapman pers. comm.), but all the specimens of Titanichthys the author has examined are either similar in size to AMNH FF 7134 or are much smaller in size (e.g., those figured in Dunkle and Bungart 1942).

As can be seen from these results, Titanichthys is roughly comparable in size to Dunkleosteus, accounting for the fact that there are fewer individuals known from anatomically identifiable material.

17.8 Body mass of Rhizodus hibberti

data.frame(total_length=563,precaudal_length=456.78,rowname="Using proportions of juvenile Strepsodus",habitat="benthic",
           body_depth=89.55,body_width=89.55,head_length=104.14,
           swimbladder=T,heterocercal=T)%>%
  add_row(total_length=514,precaudal_length=417,rowname="Using trunk proportions of Goolongongia",habitat="benthic",
          body_depth=76.16,body_width=76.16,head_length=104.14,
          swimbladder=T,heterocercal=T)%>%
  mutate(girth=ramanujan.approx(body_depth,body_width))%>%
  augment(fit.ellipsoid,newdata=.,interval="predict")%>%
  mutate(across(.fitted:.upper,~(exp(.)*regression.stats(fit.ellipsoid)$CF/1000)))%>%
  column_to_rownames("rowname")%>%
  select(total_length,.fitted,.lower,.upper)%>%
  kable(digits=1,align="c",
        col.names=c("Estimated Length (cm)","Est.","Lower 95% P.I.","Upper 95% P.I."),
        caption="Estimated body masses of <i>Rhizodus hibberti</i> using dimensions of the juvenile <i>Strepsodus anculonamensis</i> from Andrews (1985) and trunk proportions of <i>Goolongongia loomesi</i> from Johanson and Ahlberg (1998) (i.e., trunk depth and total length but treating trunks as cylindrical because Devonian rhizodonts unlike Carboniferous ones are dorsoventrally compressed; Jeffery, pers. comm.)")%>%
  add_header_above(c(" "=2,"Estimated Body Mass (kg)"=3))%>%
  kable_styling()
Table 17.5: Estimated body masses of Rhizodus hibberti using dimensions of the juvenile Strepsodus anculonamensis from Andrews (1985) and trunk proportions of Goolongongia loomesi from Johanson and Ahlberg (1998) (i.e., trunk depth and total length but treating trunks as cylindrical because Devonian rhizodonts unlike Carboniferous ones are dorsoventrally compressed; Jeffery, pers. comm.)
Estimated Body Mass (kg)
Estimated Length (cm) Est. Lower 95% P.I. Upper 95% P.I.
Using proportions of juvenile Strepsodus 563 1463.2 814.3 2629.1
Using trunk proportions of Goolongongia 514 1019.7 570.6 1822.1

It is possible these estimates might be overestimates if Rhizodus did not have a perfectly cylindrical trunk and instead had a trunk that was slightly deeper than wide.

18 References

Afrisal, M.; Nurjirana; Irmawati; Burhanuddin, A.I. Osteological study of Titan Trigger fish, Balistoides viridescens (Bloch and Schneider, 1801) (Balistidae: Tetraodontiformes) from the Spermonde Archipelago Waters. IOP Conference Series: Earth and Environmental Science 2019, 370, doi:10.1088/1755-1315/370/1/012035.

Alexander, R.M. Functional Design in Fishes; Hutchinson University Library: London, 1967.

Ault, J.S.; Luo, J. A reliable game fish weight estimation model for Atlantic tarpon (Megalops atlanticus). Fisheries Research 2013, 139:110-117. doi:https://doi.org/10.1016/j.fishres.2012.10.004

Baker C.F.; Rossi C.R.; Quiroga P.; White E.; Williams P.; Kitson, J; Bice, C.M.; Renaud, C.B.; Potter, I.; Neira, F.J.; and Baigún, C. Morphometric and physical characteristics distinguishing adult Patagonian lamprey, Geotria macrostoma from the pouched lamprey, Geotria australis. PLoS ONE 2019, 16(5): e0250601. https://doi.org/10.1371/journal.pone.0250601

Baldridge, H.D. Sinking Factors and Average Densities of Florida Sharks as Functions of Liver Buoyancy. Copeia 1970, 1970, 744-754, doi:10.2307/1442317.

Blanton, P.L.; Biggs, N.L. Density of fresh and embalmed human compact and cancellous bone. American Journal of Physical Anthropology 1968, 29, 39-44, doi:https://doi.org/10.1002/ajpa.1330290113.

Bone, Q. Buoyancy and Hydrodynamic Functions of Integument in the Castor Oil Fish, 8Ruvettus pretiosus* (Pisces: Gempylidae). Copeia 1972, 22, 135-156. doi:10.1017/scs.2017.12

Boyle, J.; Ryan, M.J. New information on Titanichthys (Placodermi, Arthrodira) from the Cleveland Shale (Upper Devonian) of Ohio, USA. J Paleontol 2017, 91, 318–336, doi:10.1017/jpa.2016.136.

Brassey, C.A. Body-mass estimation in paleontology: a review of volumetric techniques. The Paleontological Society Papers 2016, 22, 135-156. doi:10.1017/scs.2017.12

Carr, R.K. Paleoecology of Dunkleosteus terrelli. Kirtlandia 2010, 57, 36-45.

Collette BB, and Nauen CE. Scombrids of the world: an annotated and illustrated catalogue of tunas, mackerels, bonitos, and related species known to date. Food and Agriculture Organization of the United Nations: Rome, 1983.

Compagno, L.J.V. Sharks of the world. An annotated and illustrated catalogue of shark species known to date. Part 1. Hexanchiformes to Lamniformes Food and Agriculture Organization of the United Nations: Rome, 1984.

Corner, E.D.S.; Denton, E.J.; Forster, G. On the buoyancy of some deep-sea sharks. Proceedings of the Royal Society of London. Series B. Biological Sciences 1969, 171, 415-429, doi:10.1098/rspb.1969.0003

Cupello, C.; Brito, P.M.; Herbin, M.; Meunier, F.J.; Janvier, P.; Dutel, H.; Clément, G. Allometric growth in the extant coelacanth lung during ontogenetic development. Nature Communications 2015, 6, 8222, doi:10.1038/ncomms9222.

Dean, B. Biometric Evidence in the Problem of the Paired Limbs of the Vertebrates. American Naturalist 1902, 36, 837-847

Dean, B. Notes on a newly mounted Titanichthys. Memoirs of the American Museum of Natural History 1909, 5, 270–271.

De Maddalena, A.; Glaizot, O.; Olivier, G. On the Great White Shark, Carcharodon carcharias (Linnaeus, 1758), preserved in the Museum of Zoology in Lausanne. Marine Life 2003, 13, 53-59.

Dunkle, D. H.; Bungart, P. A. The infero-gnathal plates of Titanichthys. Scientific Publications of the Cleveland Museum of Natural History 1942, 8, 49-59.

Engelman, R.K. Giant, swimming mouths: oral dimensions of extant sharks do not accurately predict body size in Dunkleosteus terrelli (Placodermi: Arthrodira). In Press.

Engelman, R.K. Reconstruction of Dunkleosteus terrelli (Placodermi: Arthrodira): a new look for the Devonian’s most famous predator. In Prep.

Ferrón, H.G.; Martínez-Pérez, C.; Botella, H. Ecomorphological inferences in early vertebrates: reconstructing Dunkleosteus terrelli (Arthrodira, Placodermi) caudal fin from palaeoecological data. PeerJ 2017, 5, e4081, doi:10.7717/peerj.4081

Freedman, J.A.; Noakes, D.L.G. Why are there no really big bony fishes? A point-of-view on maximum body size in teleosts and elasmobranchs. Reviews in Fish Biology and Fisheries 2002, 12, 403-416, doi:https://doi.org/10.1023/A:1025365210414

Froese, R. Cube law, condition factor and weight–length relationships: history, meta-analysis and recommendations. Journal of Applied Ichthyology 2006, 22, 241-253, doi:https://doi.org/10.1111/j.1439-0426.2006.00805.x.

Goujet, D. “Lungs” in Placoderms, a persistent palaeobiological myth related to environmental preconceived interpretations. Comptes Rendus Palevol 2011, 10, 323–329, doi:https://doi.org/10.1016/j.crpv.2011.03.008.

Hussakof, L. Notes on the Devonian “placoderm” Dinichthys intermedius Newb. Bulletin of the American Museum of Natural History 1905, 21, 27-36.

Kauffman, D.E. Notes on the biology of the tiger shark (Galeocerdo arcticus) from Philippine waters; 16; U. S. Fish & Wildlife Service: Washington D.C., 1950; pp. 1- 10.

ICRP. Basic anatomical and physiological data: The skeleton. Annals of the ICRP 1995, 25, 1-80, doi:10.1016/S0146-6453(00)80004-4.

McCune, A.R.; Carlson, R.L. Twenty ways to lose your bladder: common natural mutants in zebrafish and widespread convergence of swim bladder loss among teleost fishes. Evolution and Development 2004, 6, 246-259, doi:https://doi.org/10.1111/j.1525-142X.2004.04030.x.

Miles, R.S.; Westoll, T.S. The Placoderm Fish Coccosteus cuspidatus Miller ex Agassiz from the Middle Old Red Sandstone of Scotland. Part I. Descriptive Morphology. Transactions of the Royal Society of Edinburgh 1968, 67, 373-476, doi:10.1017/S0080456800024078.

Miller, R.G. Beyond ANOVA, Basics of Applied Statistics; John Wiley and Sons: New York 1986; p. 317.

Mollet, H.F.; Cailliet, G.M. Using allometry to predict body mass from linear measurements of the white shark. In Great White Sharks: The Biology of Carcharodon carcharias, Klimley, A.P., Ainley, D.G., Eds.; Academic Press: New York, 1996; pp. 81-89.

Parsons. Displacement and Area Curves of Fishes. Transactions of the American Society of Mechanical Engineers 1888, 9, 679-695.

Ramanujan, S. Ramanujan’s Collected Works; Chelsea, New York, 1962.

Robins, C.R. Summer concentration of white marlin, Tetrapturus albidus, west of the straits of Gibraltar. In Proceedings of the international billfish symposium Kailua-Kona, Hawaii, 9-12 August 1972. Part 2. Review and Contributed Papers, Shomura, S., Williams, F., Eds.; National Oceanic and Atmospheric Administration, NOAA Technical Report NMFS SSRF-675: 1974; pp. 164-174.

Robins, C.R.; De Sylva, D.P. A new western Atlantic spearfish, Tetrapturus pfluegeri, with a redescription of the Mediterranean spearfish Tetrapturus belone. Bulletin of Marine Science of the Gulf and Caribbean 1963, 13, 85–122.

Schultze, H.-P. Juvenile specimens of Eusthenopteron foordi Whiteaves, 1881 (osteolepiform rhipidistian, Pisces) from the Late Devonian of Miguasha, Quebec, Canada. Journal of Vertebrate Paleontology 1984, 4, 1-16. https://doi.org/10.1080/02724634.1984.10011982

Shephard, R.J. Body Composition in Biological Anthropology; Cambridge University Press: Cambridge, 1991; p. 368.

Springer, S.; Gilbert, P.W. The Basking Shark, Cetorhinus maximus, from Florida and California, with Comments on Its Biology and Systematics. Copeia 1976,1, 47-54. https://doi.org/10.2307/1443770

Trinajstic, K. New anatomical information on Holonema (Placodermi) based on material from the Frasnian Gogo Formation and the Givetian-Frasnian Gneudna Formation, Western Australia. Geodiversitas 1999, 21, 69-84.

Trinajstic, K.; Long, J.A.; Sanchez, S.; Boisvert, C.A.; Snitting, D.; Tafforeau, P.; Dupret, V.; Clement, A.M.; Currie, P.D.; Roelofs, B.; et al. Exceptional preservation of organs in Devonian placoderms from the Gogo lagerstätte. Science 2022, 377, 1311-1314, doi:10.1126/science.abf3289

Varojean, D.H. Systematics of the genus Echinorhinus Blainville, based on a study of the prickly shark Echinorhinus cookei Pietschmann. Fresno State College, Fresno, 1972.

Villarino, M.B. Ramanujan’s Perimeter of an Ellipse. ArXiv Math 2008. doi:https://doi.org/10.48550/arXiv.math/0506384

Wall, W.P. The Correlation between High Limb-Bone Density and Aquatic Habits in Recent Mammals. Journal of Paleontology 1983, 57, 197-207.

Wedel, M.J. Evidence for bird-like air sacs in saurischian dinosaurs. Journal of Experimental Zoology 2009, 311A, 611-628. doi:https://doi.org/10.1002/jez.513

19 Session Information

xfun::session_info()
## R version 4.2.1 (2022-06-23 ucrt)
## Platform: x86_64-w64-mingw32/x64 (64-bit)
## Running under: Windows 10 x64 (build 22621)
## 
## Locale: LC_COLLATE=English_United States.utf8  LC_CTYPE=English_United States.utf8    LC_MONETARY=English_United States.utf8 LC_NUMERIC=C                           LC_TIME=English_United States.utf8    
## 
## Package version:
##   askpass_1.1           assertthat_0.2.1      backports_1.4.1       base64enc_0.1.3       bit_4.0.4             bit64_4.0.5           blob_1.2.3            bookdown_0.30         broom_1.0.1           bslib_0.4.1           cachem_1.0.6          callr_3.7.3           cellranger_1.1.0      class_7.3-20          cli_3.4.1             clipr_0.8.0           colorspace_2.0-3      commonmark_1.8.1      compiler_4.2.1        cowplot_1.1.1         cpp11_0.4.3           crayon_1.5.2          curl_4.3.3            data.table_1.14.4     DBI_1.1.3             dbplyr_2.2.1          digest_0.6.30         dplyr_1.0.10          dtplyr_1.2.2          e1071_1.7-12          ellipsis_0.3.2        evaluate_0.18         fansi_1.0.3           farver_2.1.1          fastmap_1.1.0         forcats_0.5.2         fs_1.5.2              gargle_1.2.1          generics_0.1.3        ggplot2_3.4.0         ggstar_1.0.4          ggtext_0.1.2          glue_1.6.2            googledrive_2.0.0     googlesheets4_1.0.1  
##   graphics_4.2.1        grDevices_4.2.1       grid_4.2.1            gridExtra_2.3         gridtext_0.1.5        gtable_0.3.1          haven_2.5.1           highr_0.9             hms_1.1.2             htmltools_0.5.3       httr_1.4.4            ids_1.0.1             isoband_0.2.7         jpeg_0.1.9            jquerylib_0.1.4       jsonlite_1.8.3        kableExtra_1.3.4      knitr_1.40            labeling_0.4.2        lattice_0.20-45       lifecycle_1.0.3       lubridate_1.9.0       magick_2.7.3          magrittr_2.0.3        markdown_1.3          MASS_7.3.58.1         Matrix_1.5-1          memoise_2.0.1         methods_4.2.1         mgcv_1.8-40           mime_0.12             modelr_0.1.10         munsell_0.5.0         nlme_3.1-160          openssl_2.0.4         openxlsx_4.2.5.1      pillar_1.8.1          pkgconfig_2.0.3       png_0.1.7             prettyunits_1.1.1     processx_3.8.0        progress_1.2.2        proxy_0.4-27          ps_1.7.2              purrr_0.3.5          
##   R6_2.5.1              ragg_1.2.4            rappdirs_0.3.3        RColorBrewer_1.1.3    Rcpp_1.0.9            readr_2.1.3           readxl_1.4.1          rematch_1.0.1         rematch2_2.1.2        reprex_2.0.2          rlang_1.0.6           rmarkdown_2.18        rmdformats_1.0.4.9000 rstudioapi_0.14       rvest_1.0.3           sass_0.4.2            scales_1.2.1          selectr_0.4.2         splines_4.2.1         stats_4.2.1           stringi_1.7.8         stringr_1.4.1         svglite_2.1.0         sys_3.4.1             systemfonts_1.0.4     textshaping_0.3.6     tibble_3.1.8          tidyr_1.2.1           tidyselect_1.2.0      tidyverse_1.3.2       timechange_0.1.1      tinytex_0.42          tools_4.2.1           tzdb_0.3.0            utf8_1.2.2            utils_4.2.1           uuid_1.1.0            vctrs_0.5.0           viridisLite_0.4.1     vroom_1.6.0           webshot_0.5.4         withr_2.5.0           xfun_0.34             xml2_1.3.3            yaml_2.3.6           
##   zip_2.2.2
LS0tDQp0aXRsZTogIlN1cHBsZW1lbnRhcnkgSW5mb3JtYXRpb24gZm9yIg0Kc3VidGl0bGU6ICInQSBEZXZvbmlhbiBGaXNoIFRhbGU6IEEgTmV3IE1ldGhvZCBvZiBCb2R5IExlbmd0aCBFc3RpbWF0aW9uIFN1Z2dlc3RzIE11Y2ggU21hbGxlciBTaXplcyBmb3IgPGk+RHVua2xlb3N0ZXVzIHRlcnJlbGxpPC9pPiAoUGxhY29kZXJtaTogQXJ0aHJvZGlyYSkgJyINCmF1dGhvcjogIlJ1c3NlbGwgRW5nZWxtYW4iDQpkYXRlOiAiMS8yOC8yMDIyIg0Kb3V0cHV0Og0KICBybWRmb3JtYXRzOjpodG1sX2NsZWFuOg0KICAgIGhpZ2hsaWdodDogaGFkZG9jaw0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19kZXB0aDogMw0KICAgIHRvY19mbG9hdDogVFJVRQ0KICAgIG51bWJlcl9zZWN0aW9uczogVFJVRQ0KICAgIHRodW1ibmFpbHM6IEZBTFNFDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgRFQ6IGRhdGF0YWJsZQ0KICAgIHVzZV9ib29rZG93bjogVFJVRQ0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCi0tLQ0KPHN0eWxlPg0KYm9keSAubWFpbi1jb250YWluZXIgew0KICAgICAgICBtYXgtd2lkdGg6IDEwMDBweDsNCiAgICB9DQojdG9jIHsNCiAgd2lkdGg6IDE1JTsNCn0NCjwvc3R5bGU+DQoNCioqSW1wb3J0YW50IE5vdGU6KiogSWYgdGhlIFIgY29kZSAoLnJtZCBmaWxlKSBkb2VzIG5vdCB3b3JrIHdoZW4gdHJ5aW5nIHRvIHJlcnVuIHRoZSBhbmFseXNpcywgb25lIGlzc3VlIG1heSBiZSB0aGF0IHRoZSBkYXRhc2V0cyAodGhlIC54bHN4IGFuZC9vciAuY3N2IGZpbGVzKSBhbmQgYWRkaXRpb25hbCBpbWFnZXMgKGkuZS4sIHRoZSAucG5nIGZpbGVzKSBtdXN0IGJlIGluIHRoZSBzYW1lIHdvcmtpbmcgZW52aXJvbm1lbnQgKHVzdWFsbHkgdGhlIHNhbWUgZm9sZGVyKSBhcyB0aGUgLnJtZCBmaWxlLiBJZiBwb3NzaWJsZSwgbWFrZSBzdXJlIHRoZSBzdXBwbGVtZW50YXJ5IGZpZ3VyZXMgYXJlIGluIHRoZSBzYW1lIGZvbGRlciBhcyB0aGUgUiBDb2RlLiBJbiB0aGUgZXZlbnQgdGhhdCB0aGlzIGlzIG5vdCBwb3NzaWJsZSwgZGVhY3RpdmF0ZSB0aGUgY2h1bmtzIHRoYXQgY29udGFpbiByZWZlcmVuY2VzIHRvIHN1cHBsZW1lbnRhcnkgZmlndXJlcyBvciBkZWxldGUgdGhlc2UgbGluZXMgZnJvbSB0aGUgUiBDb2RlLg0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSxmaWcud2lkdGg9NyxmaWcuYXNwPTAuNzUsd2FybmluZz1GLHRpbWVfaXQgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgIGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnKQ0KbGlicmFyeShyZWFkeGwpO2xpYnJhcnkoZ2dzdGFyKTtsaWJyYXJ5KGthYmxlRXh0cmEpO2xpYnJhcnkoZ3JpZEV4dHJhKTtsaWJyYXJ5KHNjYWxlcyk7bGlicmFyeShtYWdyaXR0cik7bGlicmFyeShicm9vbSk7bGlicmFyeShybGFuZyk7bGlicmFyeShjb3dwbG90KTtsaWJyYXJ5KG1hZ2ljayk7bGlicmFyeShlMTA3MSk7bGlicmFyeShvcGVueGxzeCk7DQpsaWJyYXJ5KGdndGV4dCkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KIyBGb3JtdWxhcyBmb3IgcmVncmVzc2lvbiBzdGF0aXN0aWNzDQpyZWdyZXNzaW9uLnN0YXRzPC1mdW5jdGlvbihmaXQpew0KICBmb3JtdWxhPC1maXQkY2FsbDsNCiAgbG9nX3RyYW5zPC1kcGx5cjo6Y2FzZV93aGVuKHN1YnN0cihjb2xuYW1lcyhmaXQkbW9kZWxbMV0pLDEsNSk9PSJsb2cxMCJ+ImxvZzEwIiwNCiAgICAgICAgICAgICAgICAgICAgICAgc3Vic3RyKGNvbG5hbWVzKGZpdCRtb2RlbFsxXSksMSw0KT09ImxvZygifiJsbiIsDQogICAgICAgICAgICAgICAgICAgICAgIFRSVUV+Im5vbmUiKTsNCiAgeTwtc3dpdGNoKGxvZ190cmFucywibG9nMTAiPTEwXmZpdCRtb2RlbFssMV0sImxuIj1leHAoZml0JG1vZGVsWywxXSksIm5vbmUiPWZpdCRtb2RlbFssMV0pOw0KICBmaXR0ZWQ8LXN3aXRjaChsb2dfdHJhbnMsImxvZzEwIj0xMF5maXR0ZWQoZml0KSwibG4iPWV4cChmaXR0ZWQoZml0KSksIm5vbmUiPWZpdHRlZChmaXQpKTsNCiAgYWJzZXJyb3I8LWFicyhmaXR0ZWQteSkvZml0dGVkOw0KICBRTUxFPC1pZmVsc2UobG9nX3RyYW5zPT0ibm9uZSIsDQogICAgICAgICAgICAgICBOQSwNCiAgICAgICAgICAgICAgIGlmZWxzZShsb2dfdHJhbnM9PSJsb2cxMCIsDQogICAgICAgICAgICAgICAgICAgICAgMTBeKChzaWdtYShmaXQpXjIpLzIpLA0KICAgICAgICAgICAgICAgICAgICAgIGV4cCgoc2lnbWEoZml0KV4yKS8yKSkpOw0KICBzbWVhcjwtaWZlbHNlKGxvZ190cmFucz09Im5vbmUiLA0KICAgICAgICAgICAgICAgIE5BLA0KICAgICAgICAgICAgICAgIGlmZWxzZShsb2dfdHJhbnM9PSJsb2cxMCIsDQogICAgICAgICAgICAgICAgICAgICAgIHN1bSgxMF5maXQkcmVzaWR1YWxzKS9ucm93KGZpdCRtb2RlbCksDQogICAgICAgICAgICAgICAgICAgICAgIHN1bShleHAoZml0JHJlc2lkdWFscykpL25yb3coZml0JG1vZGVsKSkpOw0KICBSRTwtaWZlbHNlKGxvZ190cmFucz09Im5vbmUiLA0KICAgICAgICAgICAgIE5BLA0KICAgICAgICAgICAgIG1lYW4oeSkvbWVhbihmaXR0ZWQpKTsNCiAgQ0Y8LWlmZWxzZShsb2dfdHJhbnM9PSJub25lIiwNCiAgICAgICAgICAgICBOQSwNCiAgICAgICAgICAgICAoUkUrc21lYXIrUU1MRSkvMyk7DQogIGFkalBFPC1pZmVsc2UobG9nX3RyYW5zPT0ibm9uZSIsDQogICAgICAgICAgICAgICAgTkEsDQogICAgICAgICAgICAgICAgbWVhbihhYnMoKGZpdHRlZCpDRikteSkvKGZpdHRlZCpDRikpKTsNCiAgU0VFPC1pZmVsc2UobG9nX3RyYW5zPT0ibG9nMTAiLA0KICAgICAgICAgICAgICAxMF4oMitzaWdtYShmaXQpKS0xMDAsDQogICAgICAgICAgICAgIGV4cChzaWdtYShmaXQpKzQuNjA1MiktMTAwKTsNCiAgc3VtbWFyeTwtc3VtbWFyeShmaXQpOw0KICBzdGF0aXN0aWNzPC1kYXRhLmZyYW1lKCJOIj1ucm93KGZpdCRtb2RlbCksDQogICAgICAgICAgICAgICAgICAgICAgICAgImRmIj1maXQkZGYucmVzaWR1YWwsDQogICAgICAgICAgICAgICAgICAgICAgICAgInIyIj1yb3VuZChzdW1tYXJ5KGZpdCkkci5zcXVhcmVkLDQpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICJhZGpyMiI9cm91bmQoc3VtbWFyeShmaXQpJGFkai5yLnNxdWFyZWQsNCksDQogICAgICAgICAgICAgICAgICAgICAgICAgIkFJQyI9cm91bmQoQUlDKGZpdCksMCksIkJJQyI9cm91bmQoQklDKGZpdCksMCksDQogICAgICAgICAgICAgICAgICAgICAgICAgImxvZ0xpayI9cm91bmQobG9nTGlrKGZpdCksMCksDQogICAgICAgICAgICAgICAgICAgICAgICAgIlBFIj1yb3VuZChtZWFuKGFic2Vycm9yKSoxMDAsMiksUU1MRT1yb3VuZChRTUxFLDMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHNtZWFyPXJvdW5kKHNtZWFyLDMpLFJFPXJvdW5kKFJFLDMpLENGPXJvdW5kKENGLDMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICJhZGpQRSI9cm91bmQobWVhbihhZGpQRSkqMTAwLDIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICJTRUUiPXJvdW5kKFNFRSwyKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICByb3cubmFtZXM9ZGVwYXJzZShzdWJzdGl0dXRlKGZpdCkpKTsNCiAgcmV0dXJuKHN0YXRpc3RpY3MpDQp9DQpyYW1hbnVqYW4uYXBwcm94PC1mdW5jdGlvbih4LHkpIHsNCiAgbWFqb3JheGlzPXgvMjsNCiAgbWlub3JheGlzPXkvMjsNCiAgcGkqKChtYWpvcmF4aXMrbWlub3JheGlzKS0NCiAgICAgICAgKCgzKihtYWpvcmF4aXMtbWlub3JheGlzKV4yKS8NCiAgICAgICAgICAgKDEwKih4K21pbm9yYXhpcykrc3FydCh4XjIrMTQqbWFqb3JheGlzKm1pbm9yYXhpcyttaW5vcmF4aXNeMikpKSkNCn0NCmxtX2Vxbi5hbGwgPC0gZnVuY3Rpb24ocGxvdF9kYXRhKXsNCiAgZXEgPC0gc3Vic3RpdHV0ZSgibG4odG90YWwgbGVuZ3RoKSIgPT0gYSArIGIgJS4lICJsbihPT0wpIioiLCJ+fiJyIl4yfiI9In5yMiwgDQogICAgICAgICAgICAgICAgICAgbGlzdChhID0gZm9ybWF0KHVubmFtZShjb2VmKHBsb3RfZGF0YSlbMV0pLCBkaWdpdHMgPSA1KSwNCiAgICAgICAgICAgICAgICAgICAgICAgIGIgPSBmb3JtYXQodW5uYW1lKGNvZWYocGxvdF9kYXRhKVsyXSksIGRpZ2l0cyA9IDUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgcjIgPSBmb3JtYXQoc3VtbWFyeShwbG90X2RhdGEpJHIuc3F1YXJlZCwgZGlnaXRzID0gMykpKQ0KICBhcy5jaGFyYWN0ZXIoYXMuZXhwcmVzc2lvbihlcSkpDQp9DQpgYGANCg0KIyBMb2FkaW5nIGluIGRhdGENCg0KYGBge3IsY2FjaGU9VFJVRSxjYWNoZS5leHRyYSA9IHRvb2xzOjptZDVzdW0oIkRldm9uaWFuIEZpc2ggVGFsZSBTdXBwbGVtZW50YXJ5IEZpbGUgMSAoRGF0YSkueGxzeCIpfQ0KI0luZ2VzdGluZyBkYXRhDQpkYXRhX2luaXRpYWw8LXJlYWR4bDo6cmVhZF9leGNlbCgiRGV2b25pYW4gRmlzaCBUYWxlIFN1cHBsZW1lbnRhcnkgRmlsZSAxIChEYXRhKS54bHN4IiklPiUNCiAgZmlsdGVyKGlzLm5hKGRyb3ApKSAlPiUNCiAgcmVuYW1lKGNsYWRlPUNsYWRlLGhpZ2hlcl9ncm91cD0iSGlnaGVyIEdyb3VwIixvcmRlcj1PcmRlcixmYW1pbHk9RmFtaWx5LA0KICAgICAgICAgZXh0aW5jdD1FeHRpbmN0LHNoYXBlPVNoYXBlLGhhYml0YXQ9SGFiaXRhdCwNCiAgICAgICAgIHRvdGFsX2xlbmd0aD0iVG90YWwgTGVuZ3RoIChjbSkiLHByZWNhdWRhbF9sZW5ndGg9IlByZWNhdWRhbCBMZW5ndGggKGNtKSIsDQogICAgICAgICBib2R5X21hc3M9IkJvZHkgTWFzcyAoZykiLGhlYWRfbGVuZ3RoPSJIZWFkIExlbmd0aCAoY20pIiwNCiAgICAgICAgIHNub3V0X2xlbmd0aD0iU25vdXQgTGVuZ3RoIChjbSkiLE9PTD0iT09MIChjbSkiLA0KICAgICAgICAgYm9keV93aWR0aD0iTWVkaW9sYXRlcmFsIEJvZHkgV2lkdGggKGNtKSIsDQogICAgICAgICBib2R5X2RlcHRoPSJEb3Jzb3ZlbnRyYWwgQm9keSBIZWlnaHQgKGNtKSIsDQogICAgICAgICBnZW51cz1HZW51cyxzcGVjaWVzPVNwZWNpZXMsIHNwZWNpbWVuPVNwZWNpbWVuLGxlbmd0aF9hcz0iTGVuZ3RoIEFzIiwNCiAgICAgICAgIGZvcmtfbGVuZ3RoPSJGb3JrIExlbmd0aCAoY20pIixnaXJ0aD0iR2lydGggKGNtKSIsDQogICAgICAgICBwcmVwZWN0b3JhbF9sZW5ndGg9IlByZXBlY3RvcmFsIExlbmd0aCAoY20pIiwNCiAgICAgICAgIHByZXBlbHZpY19sZW5ndGg9IlByZXBlbHZpYyBMZW5ndGggKGNtKSIsDQogICAgICAgICBtb3V0aF93aWR0aD0iTW91dGggV2lkdGggKGNtKSIsaGVhZF9kZXB0aD0iSGVhZCBIZWlnaHQgKGNtKSIsDQogICAgICAgICBzd2ltYmxhZGRlcj0iUHJlc2VuY2Ugb2YgU3dpbWJsYWRkZXIiLA0KICAgICAgICAgaGV0ZXJvY2VyY2FsPSJQcmVzZW5jZSBvZiBIZXRlcm9jZXJjYWwgQ2F1ZGFsIEZpbiIsDQogICAgICAgICBvbnRvZ2VueT0iT250b2dlbmV0aWMgU3RhdHVzIixyZWZlcmVuY2VzPSJSZWZlcmVuY2UiLA0KICAgICAgICAgaXNfYm9keV9tYXNzX2VzdGltYXRlZD0iSXMgQm9keSBNYXNzIEVzdGltYXRlZCIpJT4lDQogIG11dGF0ZSh0YXhvbiA9IHBhc3RlMChnZW51cywiXyIsc3BlY2llcyksDQogICAgICAgICBzaGFwZT1yZWxldmVsKGZhY3RvcihzaGFwZSksImZ1c2lmb3JtIiksDQogICAgICAgICBoYWJpdGF0PXJlbGV2ZWwoZmFjdG9yKGhhYml0YXQpLCJkZW1lcnNhbCIpKQ0KDQojRmlsdGVyaW5nIG91dCBrbm93biBqdXZlbmlsZXMgZnJvbSB0aGUgZmluYWwgZGF0YXNldA0KZGF0YV9maW5hbDwtZGF0YV9pbml0aWFsJT4lDQogIGZpbHRlcihvbnRvZ2VueSAlaW4lIGMoImFkdWx0Iiwic3ViYWR1bHQiKXxpcy5uYShvbnRvZ2VueSl8Y2xhZGU9PSJQbGFjb2Rlcm1pIikNCg0KZm9zc2lsLnNwZWNpbWVuczwtcXVvKGNsYWRlPT0iUGxhY29kZXJtaSImbGVuZ3RoX2FzPT0idG90YWwgbGVuZ3RoInxzcGVjaW1lbiAlaW4lIGMoIkNNTkggNzQyNCIsIkNNTkggNjA5MCIsIkNNTkggNzA1NCIsIkNNTkggNTc2OCIpKQ0KYGBgDQoNCiMjIFNlcGFyYXRpbmcgb3V0IGZvc3NpbCB0YXhhDQoNCmBgYHtyfQ0KKGZvc3NpbF90YXhhPC1kYXRhX2luaXRpYWwlPiUNCiAgZmlsdGVyKGNsYWRlPT0iUGxhY29kZXJtaSIpJT4lDQogIG11dGF0ZShzcGVjaW1lbjI9cGFzdGUwKGdlbnVzLCIgIixzcGVjaW1lbiksDQogICAgICAgICB0YXhvbj1zdHJfcmVwbGFjZSh0YXhvbiwiXyIsIiAiKSklPiUNCiAgY29sdW1uX3RvX3Jvd25hbWVzKCJzcGVjaW1lbjIiKSU+JQ0KICBhcnJhbmdlKGhlYWRfbGVuZ3RoKSU+JQ0KICBtdXRhdGUodGF4b249aWZlbHNlKGdlbnVzPT0iTmV3c3BlY2llcyIsIkdlbi4gZXQgc3AuIG5vdi4iLHRheG9uKSkpDQpgYGANCg0KIyMgQ2FsY3VsYXRpbmcgc3BlY2llcy1hdmVyYWdlIG1vZGVsDQoNCmBgYHtyfQ0KZGF0YV9zcGVjaWVzPC1kYXRhX2ZpbmFsJT4lDQogIGRyb3BfbmEodG90YWxfbGVuZ3RoLE9PTCklPiUNCiAgZmlsdGVyKGxlbmd0aF9hcyE9ImVzdGltYXRlZCB0LmwuIiklPiUNCiAgbXV0YXRlKE49aWZlbHNlKGlzLm5hKE4pLGFzLm51bWVyaWMoTiksMSkpJT4lDQogIGdyb3VwX2J5KHRheG9uKSU+JQ0KICBzdW1tYXJpc2UoYWNyb3NzKHdoZXJlKGlzLm51bWVyaWMpLCB3ZWlnaHRlZC5tZWFuLCBOKSwNCiAgICAgICAgICAgIE49c3VtKE4pLGdlbnVzPXVuaXF1ZShnZW51cyksc3BlY2llcz11bmlxdWUoc3BlY2llcyksDQogICAgICAgICAgICBjbGFkZT11bmlxdWUoY2xhZGUpLGhpZ2hlcl9ncm91cD11bmlxdWUoaGlnaGVyX2dyb3VwKSwNCiAgICAgICAgICAgIG9yZGVyPXVuaXF1ZShvcmRlciksZmFtaWx5PXVuaXF1ZShmYW1pbHkpLA0KICAgICAgICAgICAgc2hhcGU9dW5pcXVlKHNoYXBlKSxoYWJpdGF0PXVuaXF1ZShoYWJpdGF0KSklPiUNCiAgc2VsZWN0KHRheG9uLE4sdG90YWxfbGVuZ3RoLE9PTCxldmVyeXRoaW5nKCkpDQoNCndyaXRlLmNzdihkYXRhX3NwZWNpZXMsImRhdGFfc3BlY2llcy5jc3YiLHJvdy5uYW1lcz1GKQ0KYGBgDQoNCiMgQ29kZSBib29rDQoNCiMjIE1lYXN1cmVtZW50cw0KDQpNZWFzdXJlbWVudCB8IFZhcmlhYmxlIE5hbWUgfCBEZWZpbml0aW9uDQotLS0tLS0tLS0tLSB8IC0tLS0tLS0tLS0tLS0gfCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQpUb3RhbCBsZW5ndGggfCB0b3RhbF9sZW5ndGggfCBMZW5ndGggZnJvbSBhbnRlcmlvciB0aXAgb2Ygcm9zdHJ1bSB0byBwb3N0ZXJpb3IgdGlwIG9mIGNhdWRhbCBmaW4NCkZvcmsgbGVuZ3RoIHwgZm9ya19sZW5ndGggfCBMZW5ndGggZnJvbSBhbnRlcmlvciB0aXAgb2Ygcm9zdHJ1bSB0byBub3RjaCBpbiBjYXVkYWwgZm9yayBiZXR3ZWVuIHVwcGVyIGFuZCBsb3dlciBjYXVkYWwgZmluIGxvYmVzLiBDb2RlZCBhcyBgTkFgIGZvciB0YXhhIHdpdGhvdXQgYSBjYXVkYWwgbm90Y2guDQpTdGFuZGFyZCBsZW5ndGggfCBwcmVjYXVkYWxfbGVuZ3RoIHwgTGVuZ3RoIGZyb20gYW50ZXJpb3IgdGlwIG9mIHJvc3RydW0gdG8gcG9zdGVyaW9yIHRpcCBvZiBjYXVkYWwgcGVkdW5jbGUNCkhlYWQgbGVuZ3RoIHwgaGVhZF9sZW5ndGggfCBMZW5ndGggZnJvbSBhbnRlcmlvciB0aXAgb2Ygcm9zdHJ1bSB0byBwb3N0ZXJpb3IgbWFyZ2luIG9mIGJyYW5jaGlhbCBjYXZpdHkgPGJyPiAqKkZvciBzaGFya3M6KiogZnJvbSBzbm91dCB0byBvcGVuaW5nIG9mIHRlcm1pbmFsIGdpbGwgYXJjaCA8YnI+ICoqRm9yIG9zdGVpY2h0aHlhbnM6KiogZnJvbSBzbm91dCB0byBwb3N0ZXJpb3IgZW5kIG9mIG9wZXJjdWx1bSA8YnI+ICoqRm9yIGFydGhyb2RpcmVzOioqIGZyb20gc25vdXQgdG8gY3JhbmlvLXRob3JhY2ljIGpvaW50DQpTbm91dCBsZW5ndGggfCBzbm91dF9sZW5ndGggfCBMZW5ndGggZnJvbSBhbnRlcmlvciB0aXAgb2Ygcm9zdHJ1bSB0byBhbnRlcmlvciBtYXJnaW4gb2Ygb3JiaXQNCk9yYml0LW9wZXJjbGUgbGVuZ3RoIChPT0wpIHwgT09MIHwgTGVuZ3RoIGZyb20gYW50ZXJpb3IgbWFyZ2luIG9mIHRoZSBvcmJpdCB0byBwb3N0ZXJpb3IgbWFyZ2luIG9mIHRoZSBicmFuY2hpYWwgY2F2aXR5IChzZWUgYGhlYWRfbGVuZ3RoYCBmb3IgbW9yZSBkZXRhaWxzKS4gRXF1aXZhbGVudCB0byBoZWFkIGxlbmd0aCBtaW51cyBzbm91dCBsZW5ndGgNCkhlYWQgZGVwdGggfCBoZWFkX2RlcHRoICB8IEhlaWdodCBvZiBoZWFkIGZyb20gZG9yc2FsIHRpcCBvZiBzdXByYW9jY2lwaXRhbCB0byB2ZW50cmFsIG1hcmdpbiBvZiBicmFuY2hpYWxzLjxzdXA+MTwvc3VwPg0KQm9keSBoZWlnaHQgfCBib2R5X2RlcHRoIHwgR3JlYXRlc3QgZG9yc292ZW50cmFsIGhlaWdodCBvZiB0aGUgYm9keQ0KQm9keSB3aWR0aCB8IGJvZHlfd2lkdGggfCBHcmVhdGVzdCBtZWRpb2xhdGVyYWwgd2lkdGggb2YgdGhlIGJvZHkNCkdpcnRoIHwgZ2lydGggfCBncmVhdGVzdCBjaXJjdW1mZXJlbmNlIG9mIHRoZSBib2R5IGluIHRyYW5zdmVyc2Ugdmlldw0KUHJlLXBlY3RvcmFsIGxlbmd0aCB8IHByZXBlY3RvcmFsX2xlbmd0aCB8IExlbmd0aCBmcm9tIHRpcCBvZiBzbm91dCB0byBvcmlnaW4gb2YgcGVjdG9yYWwgZmluIGFsb25nIGFudGVyb3Bvc3RlcmlvciBheGlzDQpQcmUtcGVsdmljIGxlbmd0aCB8IHByZXBlbHZpY19sZW5ndGggfCBMZW5ndGggZnJvbSB0aXAgb2Ygc25vdXQgdG8gb3JpZ2luIG9mIHBlbHZpYyBmaW4gYWxvbmcgYW50ZXJvcG9zdGVyaW9yIGF4aXMNCkJpbGwgbGVuZ3RoIHwgYmlsbF9sZW5ndGggfCBMZW5ndGggdGhhdCB0aGUgYmlsbCBwcm90cnVkZXMgYW50ZXJpb3IgdG8gdGhlIG1vdXRoIGluIElzdGlvcGhvcmlmb3JtZXMgYW5kIEhlbWlyaGFtcGhpZGFlLiBUaGlzIHZhbHVlIHdhcyBzdWJ0cmFjdGVkIGZyb20gdG90YWwgbGVuZ3RoIGFuZCBhbGwgb3RoZXIgbWVhc3VyZW1lbnRzIHRoYXQgbWVhc3VyZWQgZnJvbSB0aGUgdGlwIG9mIHRoZSBzbm91dCBiZWZvcmUgdGhlIHN0YXJ0IG9mIHRoZSBzdHVkeS4NCg0KPHN1cD4xPC9zdXA+IC0gTm90ZSB0aGF0IHdoZW4gcG9zc2libGUgYGhlYWRfZGVwdGhgIGluIGNob25kcmljaHRoeWFucyB3YXMgbWVhc3VyZWQgYXQgdGhlIHBvc3RlcmlvciBtYXJnaW4gb2YgdGhlIGNob25kcm9jcmFuaXVtIHRvIGFsbG93IGZvciBtb3JlIGRpcmVjdCBjb21wYXJpc29ucyB3aXRoIG90aGVyIGZpc2hlcywgYnV0IG1vc3QgcHJpb3IgYXV0aG9ycyBmb2xsb3cgW0NvbXBhZ25vICgxOTg0KV0oI3JlZmVyZW5jZXMpIGluIHRha2luZyB0aGlzIG1lYXN1cmVtZW50IGFzIHRoZSBkZXB0aCBhdCB0aGUgb3JpZ2luIG9mIHRoZSBwZWN0b3JhbCBmaW4uDQoNCiMjIERlZmluaXRpb25zIGZvciBgaGFiaXR1c2ANCg0KVGVybSB8IERlZmluaXRpb24NCi0tLS0gfCAtLS0tLS0tLS0tLS0tLS0tLS0tLQ0KQmVudGhpYyB8IFNwZW5kIGxhcmdlIGFtb3VudHMgb2YgdGltZSBvbiB0aGUgYm90dG9tIG9mIGJvZGllcyBvZiB3YXRlciBhbmQgYXJlIG9mdGVuIHBvb3Igb3IgaW5mcmVxdWVudCBzd2ltbWVycw0KRGVtZXJzYWwgfCBBY3RpdmUgc3dpbW1lcnMsIGJ1dCB0eXBpY2FsbHkgbGl2ZSBjbG9zZSB0byB0aGUgc2VhIGZsb29yLiBPZnRlbiBsaXZlIGluIGFuZCBhcm91bmQgY29tcGxleCBlbnZpcm9ubWVudHMgbGlrZSB1bmRlcndhdGVyIHZlZ2V0YXRpb24sIGNvcmFsIHJlZWZzLCBvciByb2NrIGNyZXZpY2VzDQpOZXJpdGljIHwgQWN0aXZlIHN3aW1tZXJzIGluIHRoZSB3YXRlciBjb2x1bW4sIGJ1dCBsaXZlIGluIHNoYWxsb3dlciB3YXRlcnMgY2xvc2VyIHRvIHRoZSBzaG9yZSAuIFdoZW4gYXNzb2NpYXRlZCB3aXRoIGNvbXBsZXggaGFiaXRhdHMgbGlrZSByZWVmcywgcm9jayBvdXRjcm9wcywgb3Igc2Vhd2VlZCAgICAgYmVkcyBhcmUgdHlwaWNhbGx5IGZvdW5kICphYm92ZSogdGhlIHN0cnVjdHVyZSByYXRoZXIgdGhhbiBsaXZpbmcgd2l0aGluIGl0DQpQZWxhZ2ljIHwgSW5oYWJpdHMgb3BlbiB3YXRlcnMgZmFyIGZyb20gYW55IHNob3JlbGluZSwgc3VjaCBhcyB0aGUgb3BlbiBvY2VhbiBvciB0aGUgb3BlbiB3YXRlcnMgb2YgbGFyZ2Ugcml2ZXJzIG9yIGxha2VzLiBPZnRlbiBoaWdobHkgbWlncmF0b3J5IG9yIG9jZWFub2Ryb211cw0KDQpOb3RhYmx5LCB0aGVzZSBzdGF0ZXMgYXJlIGFuIGF0dGVtcHQgdG8gY2F0ZWdvcml6ZSBhIHZlcnkgYnJvYWQgZWNvbG9naWNhbCBzcGVjdHJ1bSB0aGF0IGdvZXMgZnJvbSBiZW50aGljICZoYXJyOyBkZW1lcnNhbCAmaGFycjsgbmVyaXRpYyAmaGFycjsgcGVsYWdpYywgYW5kIHRoZXJlIGFyZSBtYW55IHNwZWNpZXMgdGhhdCBjb3VsZCBiZSBhcmd1ZWQgdG8gc3RyYWRkbGUgc29tZSBvZiB0aGVzZSBib3VuZGFyaWVzIGVpdGhlciB3aXRoaW4gYSBzaW5nbGUgbGlmZSBzdGFnZSAoZS5nLiwgKlNwaHlyYWVuYSogY291bGQgYmUgY29uc2lkZXJlZCBuZXJpdGljL3BlbGFnaWMpIG9yIGF0IGRpZmZlcmVudCBwYXJ0cyBvZiB0aGVpciBsaWZlY3ljbGUgKGUuZy4sIGRpYWRyb21vdXMgZmlzaGVzIGxpa2Ugc2FsbW9uKS4gQWRkaXRpb25hbGx5LCBtYW55IGZpc2hlcyBhcmUgZGVzY3JpYmVkIGFzICJiZW50aG9wZWxhZ2ljIiwgd2hpY2ggZXNzZW50aWFsbHkgcHJvdmlkZXMgYWxtb3N0IG5vIHVzZWZ1bCBpbmZvcm1hdGlvbiBhcyB0byB0aGVpciBoYWJpdHMuICJCZW50aG9wZWxhZ2ljIiBmaXNoZXMgY291bGQgYmUgY29uc2lkZXJlZCBhcyBhbmFsb2dvdXMgdG8gZGVtZXJhbCBidXQgaW4gcHJhY3RpY2Ugc2VlbXMgdG8gZW5jb21wYXNzIGRlbWVyc2FsIGFuZCBjbGVhcmx5IG5lcml0aWMgZmlzaGVzIChlLmcuLCBtYW55IGNhcmFuZ2lkcyBsaWtlICpTZXJpb2xhKikuIEFkZGluZyB0byB0aGlzIGNvbmZ1c2lvbiwgc3R1ZGllcyBvZiBmcmVzaHdhdGVyIGZpc2hlcyB0aGF0IGFyZSBhY3RpdmUgaW4gdGhlIHdhdGVyIGNvbHVtbiBjb25zaWRlciB0aGVtIGFsbCB0byBiZSAicGVsYWdpYyIsIHdoZXJlYXMgcmVzZWFyY2hlcnMgb24gbWFyaW5lIGZpc2hlcyB3aXRoIGFuYWxvZ291cyBoYWJpdHMgd2lsbCBvZnRlbiBkZXNjcmliZSB0aGUgc2FtZSBzcGVjaWVzIGFzICJiZW50aG9wZWxhZ2ljIiBhbmQgdHJlYXQgdGhlbSBhcyBlcXVpdmFsZW50IHRvIGRlbWVyc2FsIHNwZWNpZXMgKGUuZy4sIHRoaXMgaXMgYSBjb21tb24gaXNzdWUgd2hlbiBicm93c2luZyB0aGUgc3BlY2llcyBwcm9maWxlcyBvbiBGaXNoQmFzZSkuIFRoaXMgbWFrZXMgaXQgZXhjZWVkaW5nbHkgaGFyZCB0byBtYWtlIGNvbXBhcmlzb25zIG9mIGhhYml0cyBiZXR3ZWVuIGZyZXNod2F0ZXIgYW5kIG1hcmluZSBmaXNoZXMuDQoNCkFzIGEgcmVzdWx0LCBpbiB0aGlzIHN0dWR5IGBoYWJpdGF0YCB3YXMgZGV0ZXJtaW5lZCBiYXNlZCBvbiB3aGljaCBvZiB0aGUgYWJvdmUgY2F0ZWdvcmllcyBmaXNoZXMgbW9zdCBjbG9zZWx5IGNvbmZvcm1lZCB0byBiYXNlZCBvbiB0aGVpciBuYXR1cmFsIGhpc3RvcnkuIEhvd2V2ZXIsIHRoZSBhdXRob3Igd2lzaGVzIHRvIG5vdGUgaGVyZSBob3cgc29tZSBvZiB0aGVzZSBoYWJpdGF0IGRlc2lnbmF0aW9ucyBtYXkgbmVlZCB0byBiZSByZXZpc2VkLCBkdWUgdG8gdGhlIGdlbmVyYWwgY29uZnVzaW9uIHJlZ2FyZGluZyBsaWZlIGhhYml0IGNsYXNzaWZpY2F0aW9ucyBpbiBmaXNoZXMuDQoNCiMjIERlZmluaXRpb25zIGZvciBgc2hhcGVgDQoNCk1lYXN1cmVtZW50IHwgRGVmaW5pdGlvbiB8IEV4YW1wbGUNCi0tLS0tLS0tLS0tIHwgLS0tLS0tLS0tLS0tLS0tLS0tLS0gfCAtLS0tLS0tDQpmdXNpZm9ybSB8IEdlbmVyYWxpemVkIGZpc2ggYm9keSBwbGFuIHdpdGggYSAidG9ycGVkby1saWtlIiBib2R5IHNoYXBlIHwgcmFpbmJvdyB0cm91dCAoKk9uY29yaHluY2h1cyBteWtpc3MqKQ0KZWxvbmdhdGUgfCBUcnVuayBpcyBhbnRlcm9wb3N0ZXJpb3JseSBlbG9uZ2F0ZSBidXQgbm90IHNuYWtlLWxpa2Ugb3IgdHJ1bHkgYW5ndWlsbGlmb3JtIHwgYmFycmFjdWRhICgqU3BoeXJhZW5hKiBzcHAuKQ0KYW5ndWlsbGlmb3JtIHwgRXh0cmVtZWx5IGVsb25nYXRlLCBzbmFrZS1saWtlIGJvZHksIG9mdGVuIHdpdGggcmVkdWNlZCBmaW5zIHwgQW1lcmljYW4gZWVsICgqQW5ndWlsbGEgcm9zdHJhdGEqKQ0KY29tcHJlc3NpZm9ybSB8IEJvZHkgaXMgYW50ZXJvcG9zdGVyaW9ybHkgc2hvcnQgYW5kIGRlZXAgcmVsYXRpdmUgdG8gaXRzIGxlbmd0aCwgb2Z0ZW4gZGlzY29pZCB8IGJsdWVnaWxsICgqTGVwb21pcyBtYWNyb2NoaXJ1cyopDQptYWNydXJpZm9ybSB8IFByZWNhdWRhbCBwb3J0aW9uIGlzIG5vcm1hbCBidXQgaGFzIGEgbG9uZywgc3RyYWlnaHQgIndoaXAtbGlrZSIgb3IgImxpemFyZC1saWtlIiB0YWlsLCBpbiBjb250cmFzdCB0byBhIGhldGVyb2NlcmNhbCBvciBob21vY2VyY2FsIGNhdWRhbCBmaW4uICpBbG9waWFzKiBhbmQgKlN0ZWdvc3RvbWEqIGFyZSB0cmVhdGVkIGFzIG1hY3J1cmlmb3JtIGhlcmUgYmVjYXVzZSBvZiBzaW1pbGFyaXRpZXMgaW4gYm9keSBjb25zdHJ1Y3Rpb24gKHNwZWNpZmljYWxseSwgYSBsb25nLCB3aGlwLWxpa2UgY2F1ZGFsIGZpbiB0aGF0IHJlc3VsdHMgaW4gT09MIGRyYW1hdGljYWxseSB1bmRlcmVzdGltYXRpbmcgYm9keSBsZW5ndGgpLiB8IGNoaW1hZXJhcyAoQ2hpbWFlcmlkYWUpDQoNCkRlZmluaW5nIHRoZSBgZWxvbmdhdGVgIGNhdGVnb3J5IGZvciBzaGFya3Mgd2FzIGEgYml0IGRpZmZpY3VsdC4gVGhlcmUgYXJlIHNvbWUgc2hhcmtzIHRoYXQgdmVyeSBjbGVhcmx5IGV4aGliaXQgZWxvbmdhdGVkIGJvZHkgcGxhbnMgKGUuZy4sICpJc2lzdGl1cyosIFNjeWxpb3JoaW5pZGFlLCBIZW1pc2N5bGxpZGFlLCAqRXRtb3B0ZXJ1cyopLCBidXQgYSBudW1iZXIgb2Ygb3RoZXIgc2hhcmsgZ3JvdXBzIGV4aGliaXQgYW4gYW1iaWd1b3VzIGNvbmRpdGlvbiAoKk11c3RlbHVzKiwgKkhlbWlnYWxldXMqIHNwcC4pLiBUaGlzIG1ha2VzIGl0IGRpZmZpY3VsdCB0byBjb2RlIGJvZHkgc2hhcGUsIGVzcGVjaWFsbHkgYmVjYXVzZSBzaGFya3MgaW4gZ2VuZXJhbCBleGhpYml0IGEgInN0cmV0Y2hlZCBvdXQiIGJvZHkgcGxhbiBjb21wYXJlZCB0byBvdGhlciBmaXNoZXMgKHNlZSBtYW51c2NyaXB0KS4gSG93ZXZlciwgY29tcGFyaXNvbnMgdG8gcmF5LWZpbm5lZCBmaXNoZXMgKGluIHRlcm1zIG9mIGBib2R5IGRlcHRoYC9gcHJlY2F1ZGFsIGxlbmd0aGAgYW5kIHRoZSByZXNpZHVhbHMgb2YgdGhlIE9PTCBtb2RlbCkgc3VnZ2VzdCB0aGF0IGF0IGxlYXN0IHNvbWUgb2YgdGhlc2Ugc2hhcmsgbGluZWFnZXMgZG8gZXhoaWJpdCB0cnVuayBwcm9wb3J0aW9ucyBzaW1pbGFyIHRvIGBlbG9uZ2F0ZWAgcmF5LWZpbm5lZCBmaXNoZXMsIGFuZCB0cmVhdGluZyB0aGVtIGFzIGBmdXNpZm9ybWAgaXMgc3RhdGlzdGljYWxseSBpbmFwcHJvcHJpYXRlLiBUaGVyZWZvcmUsIGEgc2hhcmsgd2FzIGNvbnNpZGVyZWQgdG8gYmUgYGVsb25nYXRlYCBpbiBib2R5IHNoYXBlIGlmIGl0IGFwcGVhcmVkIHRvIGhhdmUgYSBtdWNoIG1vcmUgZWxvbmdhdGUgdHJ1bmsgdGhhbiBpcyB0eXBpY2FsIGZvciBhbiBhdmVyYWdlIHNoYXJrLWxpa2UgYm9keSBzaGFwZSAoZS5nLiwgKkNhcmNoYXJoaW51cyosICpTcXVhbHVzKikNCg0KIyMgVGF4b25vbWljIGRpc3RyaWJ1dGlvbiBvZiBvYnNlcnZhdGlvbnMNCg0KYGBge3IsY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZSd9DQpkYXRhX2ZpbmFsJT4lDQogIGRyb3BfbmEoT09MLHRvdGFsX2xlbmd0aCklPiUNCiAgZmlsdGVyKGxlbmd0aF9hcyE9ImVzdGltYXRlZCB0LmwuIiklPiUNCiAgZ3JvdXBfYnkoY2xhZGUpJT4lDQogIHN1bW1hcmlzZShOPW4oKSxUYXhhPW5fZGlzdGluY3QodGF4b24pKSU+JQ0KICBhZGRfcm93KGNsYWRlPSJBbGwgU3BlY2llcyIsDQogICAgICAgICAgTj1kYXRhX2ZpbmFsJT4lZHJvcF9uYShPT0wsdG90YWxfbGVuZ3RoKSU+JQ0KICAgICAgICAgICAgZmlsdGVyKGxlbmd0aF9hcyE9ImVzdGltYXRlZCB0LmwuIiklPiUNCiAgICAgICAgICAgIHN1bW1hcmlzZShOPW4oKSklPiVwdWxsKCksDQogICAgICAgICAgVGF4YT1kYXRhX2ZpbmFsJT4lZHJvcF9uYShPT0wsdG90YWxfbGVuZ3RoKSU+JQ0KICAgICAgICAgICAgZmlsdGVyKGxlbmd0aF9hcyE9ImVzdGltYXRlZCB0LmwuIiklPiUNCiAgICAgICAgICAgIHN1bW1hcmlzZShUYXhhPW5fZGlzdGluY3QodGF4b24pKSU+JXB1bGwoKSklPiUNCiAga2FibGUoY29sLm5hbWVzPWMoIkNsYWRlIiwiIyBPY2N1cmVuY2VzIiwiIyBUYXhhIiksDQogICAgICAgIGFsaWduPWMoImwiLCJjIiwiYyIpLA0KICAgICAgICBjYXB0aW9uPSJUYXhvbm9taWMgZGlzdHJpYnV0aW9uIG9mIG9ic2VydmF0aW9ucyBtYWRlIGluIHRoaXMgc3R1ZHkiKSU+JQ0KICByb3dfc3BlYyg2LCBib2xkID0gVCklPiUNCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KIyMgRmluYWwgY2xlYW5lZCBkYXRhc2V0DQoNCmBgYHtyfQ0KZGF0YV9maW5hbCU+JQ0KICBhcnJhbmdlKGNsYWRlLGhpZ2hlcl9ncm91cCxvcmRlcixmYW1pbHksZ2VudXMsc3BlY2llcykNCmBgYA0KDQojIyBTYXZpbmcgY2xlYW5lZCBkYXRhc2V0DQoNCmBgYHtyfQ0Kd3JpdGUuY3N2KGRhdGFfZmluYWwsZmlsZT0ib29sX2RhdGFfY2xlYW5lZC5jc3YiKQ0KYGBgDQoNCiMgQ29ycmVsYXRpb24gb2YgaGVhZCBhbmQgYm9keSBwcm9wb3J0aW9ucw0KDQoocmVmOmhlYWRkZXB0aCkgUGxvdCBvZiByZWxhdGl2ZSBoZWFkIGFuZCBib2R5IHByb3BvcnRpb25zIGluIGZpc2hlcywgc2hvd2luZyBob3cgdGhlIGxlbmd0aC1oZWlnaHQgcmF0aW8gb2YgdGhlIGhlYWQgYW5kIGJvZHkgYXJlIGNsb3NlbHkgY29ycmVsYXRlZCB3aXRoIG9uZSBhbm90aGVyLiBUaGlzIGlzIHRoZSBjYXNlIHJlZ2FyZGxlc3Mgb2Ygd2hldGhlciAoKkEqKSB0aGUgcHJvcG9ydGlvbiBiZXR3ZWVuIGhlYWQgbGVuZ3RoIGFuZCBoZWlnaHQgaXMgY29tcGFyZWQgdG8gdGhlIHByb3BvcnRpb24gYmV0d2VlbiB0b3RhbCBsZW5ndGggYW5kIGJvZHkgaGVpZ2h0LCAoKkIqKSBoZWFkIGxlbmd0aCBhbmQgdG90YWwgbGVuZ3RoIGFyZSBzY2FsZWQgdG8gZ3JlYXRlc3QgYm9keSBoZWlnaHQsIG9yICgqQyopIGhlYWQgbGVuZ3RoIGFuZCBoZWlnaHQgYXJlIGNvbXBhcmVkIHRvIHRoZSBwcm9wb3J0aW9uIGJldHdlZW4gcHJlY2F1ZGFsL3N0YW5kYXJkIGxlbmd0aCBhbmQgYm9keSBkZXB0aCByYXRoZXIgdGhhbiB0b3RhbCBsZW5ndGguICgqRCopIHNob3dzIHRoaXMgY29ycmVsYXRlZCByZW1haW5zIGV2ZW4gd2hlbiBjb25zaWRlcmluZyBPT0wgYXMgYSBwcm94eSBmb3IgaGVhZCBsZW5ndGgsIHdpdGggdGhlIGV4Y2VwdGlvbiBvZiBBY2FudGh1cmlmb3JtZXMsICBCYWxpc3RvaWRlaSwgYW5kICpTZWxlbmUgdm9tZXIqIChvbWl0dGVkIGZvciBjbGFyaXR5IGluICpEKikuDQoNCmBgYHtyLGZpZy5hc3A9MSxmaWcud2lkdGg9MTAsZmlnLmNhcD0iKHJlZjpoZWFkZGVwdGgpIixjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30NCmdyaWQuYXJyYW5nZShuY29sPTIsDQogIGdncGxvdChkYXRhX2ZpbmFsJT4lZmlsdGVyKCFpcy5uYShoZWFkX2RlcHRoKSxsZW5ndGhfYXM9PSJ0b3RhbCBsZW5ndGgiKSwNCiAgICAgICAgIGFlcyh4PWJvZHlfZGVwdGgvdG90YWxfbGVuZ3RoLHk9aGVhZF9kZXB0aC9oZWFkX2xlbmd0aCkpKw0KICAgIGdlb21fcG9pbnQoYWVzKGNvbG9yPWNsYWRlLHNoYXBlPWNsYWRlKSkrDQogICAgZ2VvbV9zbW9vdGgoZm9ybXVsYT15fngsbWV0aG9kPSJsbSIpKw0KICAgIHNjYWxlX3hfY29udGludW91cyhsYWJlbHM9c2NhbGVzOjpsYWJlbF9wZXJjZW50KGFjY3VyYWN5ID0gMSkpKw0KICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHM9c2NhbGVzOjpsYWJlbF9wZXJjZW50KGFjY3VyYWN5ID0gMSkpKw0KICAgIGdndGl0bGUoIkEiKSsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSwNCiAgZ2dwbG90KGRhdGFfZmluYWwlPiVmaWx0ZXIoIWlzLm5hKGhlYWRfZGVwdGgpLGxlbmd0aF9hcz09InRvdGFsIGxlbmd0aCIpLA0KICAgICAgICAgYWVzKHg9Ym9keV9kZXB0aC90b3RhbF9sZW5ndGgseT1ib2R5X2RlcHRoL2hlYWRfbGVuZ3RoKSkrDQogICAgZ2VvbV9wb2ludChhZXMoY29sb3I9Y2xhZGUsc2hhcGU9Y2xhZGUpKSsNCiAgICBnZW9tX3Ntb290aChmb3JtdWxhPXl+eCxtZXRob2Q9ImxtIikrDQogICAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscz1zY2FsZXM6OmxhYmVsX3BlcmNlbnQoYWNjdXJhY3kgPSAxKSkrDQogICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscz1zY2FsZXM6OmxhYmVsX3BlcmNlbnQoYWNjdXJhY3kgPSAxKSkrDQogICAgZ2d0aXRsZSgiQiIpKw0KICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpLA0KICBnZ3Bsb3QoZGF0YV9maW5hbCU+JWZpbHRlcighaXMubmEoaGVhZF9kZXB0aCksbGVuZ3RoX2FzPT0idG90YWwgbGVuZ3RoIiksDQogICAgICAgICBhZXMoeD1ib2R5X2RlcHRoL3ByZWNhdWRhbF9sZW5ndGgseT1oZWFkX2RlcHRoL2hlYWRfbGVuZ3RoKSkrDQogICAgZ2VvbV9wb2ludChhZXMoY29sb3I9Y2xhZGUsc2hhcGU9Y2xhZGUpKSsNCiAgICBnZW9tX3Ntb290aChmb3JtdWxhPXl+eCxtZXRob2Q9ImxtIikrDQogICAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscz1zY2FsZXM6OmxhYmVsX3BlcmNlbnQoYWNjdXJhY3kgPSAxKSkrDQogICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscz1zY2FsZXM6OmxhYmVsX3BlcmNlbnQoYWNjdXJhY3kgPSAxKSkrDQogICAgZ2d0aXRsZSgiQyIpKw0KICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpLA0KICBnZ3Bsb3QoZGF0YV9maW5hbCU+JWZpbHRlcighaXMubmEoaGVhZF9kZXB0aCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICFmYW1pbHkgJWluJSBjKCJCYWxpc3RpZGFlIiwiTW9uYWNhbnRoaWRhZSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAhb3JkZXIgJWluJSBjKCJBY2FudGh1cmlmb3JtZXMiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VudXMhPSJTZWxlbmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGhfYXM9PSJ0b3RhbCBsZW5ndGgiKSwNCiAgICAgICAgIGFlcyh4PWJvZHlfZGVwdGgvdG90YWxfbGVuZ3RoLHk9aGVhZF9kZXB0aC9PT0wpKSsNCiAgICBnZW9tX3BvaW50KGFlcyhjb2xvcj1jbGFkZSxzaGFwZT1jbGFkZSkpKw0KICAgIGdndGl0bGUoIkQiKSsNCiAgICBnZW9tX3Ntb290aChmb3JtdWxhPXl+eCxtZXRob2Q9ImxtIikrDQogICAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscz1zY2FsZXM6OmxhYmVsX3BlcmNlbnQoYWNjdXJhY3kgPSAxKSkrDQogICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscz1zY2FsZXM6OmxhYmVsX3BlcmNlbnQoYWNjdXJhY3kgPSAxKSkrDQogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPWMoMC44LDAuMikpKw0KICAgIGxhYnMoY29sb3I9IkNsYWRlIixzaGFwZT0iQ2xhZGUiKQ0KICApDQpgYGANCg0KTm90ZSB0aGF0IHRoZSB2YWx1ZXMgbWVhc3VyZWQgb24gdGhlIGF4ZXMgaW4gdGhlc2UgZ3JhcGhzIGFyZSBzaG93aW5nIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSAqcHJvcG9ydGlvbmFsKiBtZWFzdXJlbWVudHMgYmV0d2VlbiB0aGUgYXNwZWN0IHJhdGlvIG9mIHRoZSBoZWFkIGFuZCBib2R5IG9mIGEgZmlzaCwgbm90IHRoZWlyIGFic29sdXRlIHZhbHVlcy4gVGh1cywgYXMgZmlzaGVzJyBib2RpZXMgZ2V0IGxvbmdlciwgdGhlaXIgaGVhZHMgYWxzbyBnZXQgbG9uZ2VyLg0KDQpgYGB7cixjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30NCnJiaW5kKA0KICAiaGVhZCBoZWlnaHQvaGVhZCBsZW5ndGggYW5kIGJvZHkgZGVwdGgvdG90YWwgbGVuZ3RoICI9c3VtbWFyeShsbShJKGJvZHlfZGVwdGgvdG90YWxfbGVuZ3RoKX5JKGhlYWRfZGVwdGgvaGVhZF9sZW5ndGgpLGRhdGFfZmluYWwpKSRyLnNxdWFyZWQsDQogICJib2R5IGRlcHRoL2hlYWQgbGVuZ3RoIGFuZCBib2R5IGRlcHRoL3RvdGFsIGxlbmd0aCI9c3VtbWFyeShsbShJKGJvZHlfZGVwdGgvdG90YWxfbGVuZ3RoKX5JKGhlYWRfZGVwdGgvdG90YWxfbGVuZ3RoKSxkYXRhX2ZpbmFsKSkkci5zcXVhcmVkLA0KICAiaGVpZ2h0IGhlaWdodC9oZWFkIGxlbmd0aCBhbmQgYm9keSBkZXB0aC9wcmVjYXVkYWwgbGVuZ3RoIj1zdW1tYXJ5KGxtKEkoYm9keV9kZXB0aC9wcmVjYXVkYWxfbGVuZ3RoKX5JKGhlYWRfZGVwdGgvaGVhZF9sZW5ndGgpLGRhdGFfZmluYWwpKSRyLnNxdWFyZWQsDQogICJoZWlnaHQgaGVpZ2h0L09PTCBhbmQgYm9keSBkZXB0aC90b3RhbCBsZW5ndGgiPXN1bW1hcnkobG0oSShib2R5X2RlcHRoL3RvdGFsX2xlbmd0aCl+SShoZWFkX2RlcHRoL09PTCksZGF0YV9maW5hbCkpJHIuc3F1YXJlZA0KKSU+JQ0KICBrYWJsZShkaWdpdHM9Myxjb2wubmFtZXM9IlIyIixjYXB0aW9uPSJSPHN1cD4yPC9zdXA+IHZhbHVlcyBmb3IgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gcmVsYXRpdmUgcHJvcG9ydGlvbnMgb2YgaGVhZCBhbmQgYm9keSwgdXNpbmcgZGlmZmVyZW50IGNyaXRlcmlhIHRvIGRlZmluZSB0aGlzIHByb3BvcnRpb24iKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQpCYXNlZCBvbiB0aGlzLCBpdCBzZWVtcyB2ZXJ5IGNsZWFyIHRoYXQgdGhlIHByb3BvcnRpb25zIG9mIHRoZSBoZWFkIGFuZCBib2R5IGluIGZpc2hlcyBhcmUgc3Ryb25nbHkgY29ycmVsYXRlZCwgd2l0aCBlbG9uZ2F0aW9uIG9mIHRoZSBoZWFkIGNsb3NlbHkgY29ycmVsYXRpbmcgdG8gZWxvbmdhdGlvbiBvZiB0aGUgYm9keSB3aGV0aGVyIGhlYWQgZWxvbmdhdGlvbiBpcyBkZWZpbmVkIHJlbGF0aXZlIHRvIGhlYWQgaGVpZ2h0IG9yIGJvZHkgaGVpZ2h0LCBhbmQgd2hldGhlciBib2R5IGxlbmd0aCBpcyBkZWZpbmVkIGFzIHByZWNhdWRhbCBsZW5ndGggb3IgdG90YWwgbGVuZ3RoLg0KDQojIENydWRlIHRvdGFsIGxlbmd0aCBlc3RpbWF0ZSB1c2luZyBoZWFkIGxlbmd0aCBpbiAqRHVua2xlb3N0ZXVzIHRlcnJlbGxpKg0KDQpgYGB7cixjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30NCmhlYWRfbGVuZ3RoX2Rpc3RyaWJ1dGlvbjwtDQogIGRhdGFfZmluYWwlPiVmaWx0ZXIoIWlzLm5hKHRvdGFsX2xlbmd0aCkpJT4lDQogIGZpbHRlcighZ2VudXMgJWluJSBjKCJQbGVzaW9iYXRpcyIsIkFwbGV0b2RvbiIpLA0KICAgICAgICAgbGVuZ3RoX2FzPT0idG90YWwgbGVuZ3RoIiklPiUNCiAgZHJvcF9uYShoZWFkX2xlbmd0aCklPiUNCiAgZ3JvdXBfYnkodGF4b24pJT4lDQogIHN1bW1hcmlzZShoZWFkX2xlbmd0aD1tZWFuKGhlYWRfbGVuZ3RoKSx0b3RhbF9sZW5ndGg9bWVhbih0b3RhbF9sZW5ndGgpKSU+JQ0KICBtdXRhdGUocGVyY2VudF9oZWFkX2xlbmd0aD1oZWFkX2xlbmd0aC90b3RhbF9sZW5ndGgpDQoNCm1lYW5faGVhZF9sZW5ndGg8LW1lYW4oaGVhZF9sZW5ndGhfZGlzdHJpYnV0aW9uJHBlcmNlbnRfaGVhZF9sZW5ndGgpDQpsb3dlcl9oZWFkX2xlbmd0aDwtbWVhbihoZWFkX2xlbmd0aF9kaXN0cmlidXRpb24kcGVyY2VudF9oZWFkX2xlbmd0aCktKDIqc2QoaGVhZF9sZW5ndGhfZGlzdHJpYnV0aW9uJHBlcmNlbnRfaGVhZF9sZW5ndGgpKQ0KdXBwZXJfaGVhZF9sZW5ndGg8LW1lYW4oaGVhZF9sZW5ndGhfZGlzdHJpYnV0aW9uJHBlcmNlbnRfaGVhZF9sZW5ndGgpKygyKnNkKGhlYWRfbGVuZ3RoX2Rpc3RyaWJ1dGlvbiRwZXJjZW50X2hlYWRfbGVuZ3RoKSkNCmRhdGEuZnJhbWUoIk1lYW4iPW1lYW5faGVhZF9sZW5ndGgqMTAwLA0KICAgICAgICAgICAiTG93ZXIgQy5JLiI9bG93ZXJfaGVhZF9sZW5ndGgqMTAwLA0KICAgICAgICAgICAiVXBwZXIgQy5JLiI9dXBwZXJfaGVhZF9sZW5ndGgqMTAwKSU+JQ0KICBrYWJsZShkaWdpdHM9MixjYXB0aW9uPSJNZWFuIHBlcmNlbnQgaGVhZCBsZW5ndGggZm9yIGFsbCBmaXNoZXMsIGFzIHdlbGwgYXMgOTUlIHByZWRpY3Rpb24gaW50ZXJ2YWwgZm9yIHRoaXMgcHJvcG9ydGlvbiIsDQogICAgICAgIGNvbC5uYW1lcz1jKCJNZWFuIiwiTG93ZXIgOTUlIFAuSS4iLCJMb3dlciA5NSUgUC5JLiIpKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQoocmVmOnBlcmNlbnRoZWFkbGVuZ3RoKSBIaXN0b2dyYW0gb2YgcGVyY2VudCBoZWFkIGxlbmd0aCBpbiBmaXNoZXMsIHVzaW5nIHNwZWNpZXMgYXZlcmFnZXMgb2YgYWR1bHQgYW5kIHN1YmFkdWx0IGluZGl2aWR1YWxzIGluIHRoZSBwcmVzZW50IGRhdGFzZXQuIERhc2hlZCBsaW5lIHJlcHJlc2VudHMgbWVhbiBwZXJjZW50IGhlYWQgbGVuZ3RoIGFuZCBkb3R0ZWQgbGluZXMgcmVwcmVzZW50IHBlcmNlbnQgaGVhZCBsZW5ndGhzIGF0ICsvLSB0d28gc3RhbmRhcmQgZGV2aWF0aW9ucyBvZiB0aGUgbWVhbiB2YWx1ZS4NCg0KYGBge3IsZmlnLmNhcD0iKHJlZjpwZXJjZW50aGVhZGxlbmd0aCkiLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KZ2dwbG90KGhlYWRfbGVuZ3RoX2Rpc3RyaWJ1dGlvbixhZXMocGVyY2VudF9oZWFkX2xlbmd0aCkpKw0KICBnZW9tX2hpc3RvZ3JhbShjb2w9ImJsYWNrIixmaWxsPSJncmF5IixiaW5zPTMwKSsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PW1lYW5faGVhZF9sZW5ndGgsDQogICAgICAgICAgICAgbGluZXR5cGU9ImRhc2hlZCIpKw0KICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9bG93ZXJfaGVhZF9sZW5ndGgsDQogICAgICAgICAgICAgbGluZXR5cGU9ImRvdHRlZCIsYWxwaGE9MC41KSsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PXVwcGVyX2hlYWRfbGVuZ3RoLA0KICAgICAgICAgICAgIGxpbmV0eXBlPSJkb3R0ZWQiLGFscGhhPTAuNSkrDQogIGxhYnMoeD0iUGVyY2VudCBIZWFkIExlbmd0aCIseT0iTnVtYmVyIG9mIFNwZWNpZXMiKSsNCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscz1zY2FsZXM6OmxhYmVsX3BlcmNlbnQoYWNjdXJhY3kgPSAxKSkrDQogIHRoZW1lX2NsYXNzaWMoKQ0KYGBgDQoNCkZyb20gdGhpcyBpdCBpcyBwb3NzaWJsZSB0byBkZXRlcm1pbmUgdGhhdCBpbiBmaXNoZXMgdGhlIHRvdGFsIGhlYWQgbGVuZ3RoIGlzLCBvbiBhdmVyYWdlLCBhYm91dCAyMi43JSBvZiB0aGUgdG90YWwgbGVuZ3RoIG9mIHRoZSBvcmdhbmlzbS4NCg0KYGBge3IsY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZSd9DQpmb3NzaWxfdGF4YSU+JQ0KICBmaWx0ZXIoc3BlY2ltZW4gJWluJSBjKCJDTU5IIDc0MjQiLCJDTU5IIDYwOTAiLCJDTU5IIDcwNTQiLCJDTU5IIDU3NjgiKSklPiUNCiAgbXV0YXRlKGNydWRlX3RvdGFsX2xlbmd0aD1oZWFkX2xlbmd0aC9tZWFuX2hlYWRfbGVuZ3RoLA0KICAgICAgICAgY3J1ZGVfdG90YWxfbGVuZ3RoX2xvd2VyPWhlYWRfbGVuZ3RoL3VwcGVyX2hlYWRfbGVuZ3RoLA0KICAgICAgICAgY3J1ZGVfdG90YWxfbGVuZ3RoX3VwcGVyPWhlYWRfbGVuZ3RoL2xvd2VyX2hlYWRfbGVuZ3RoKSU+JQ0KIyBUaGUgdHdvIGFyZSByZXZlcnNlZCBoZXJlIGJlY2F1c2UgYSBzbWFsbGVyICUgaGVhZCBsZW5ndGggcmVzdWx0cyBpbiBhIGxvbmdlciBib2R5DQogIGFycmFuZ2UoaGVhZF9sZW5ndGgpJT4lDQogIHJvd25hbWVzX3RvX2NvbHVtbigpJT4lDQogIHNlbGVjdChzcGVjaW1lbixoZWFkX2xlbmd0aCxjcnVkZV90b3RhbF9sZW5ndGgsDQogICAgICAgICBjcnVkZV90b3RhbF9sZW5ndGhfbG93ZXIsY3J1ZGVfdG90YWxfbGVuZ3RoX3VwcGVyKSU+JQ0KICBrYWJsZShkaWdpdHM9MSxhbGlnbj1jKCJsIiwiYyIsImMiLCJjIiksDQogICAgICAgIGNvbC5uYW1lcyA9IGMoIlNwZWNpbWVuIiwiSGVhZCBMZW5ndGgiLCJFc3QuIExlbmd0aCIsIkxvd2VyIiwiVXBwZXIiKSwNCiAgICAgICAgY2FwdGlvbj0iRXN0aW1hdGUgb2YgdG90YWwgbGVuZ3RoIGluIDxpPkR1bmtsZW9zdGV1cyB0ZXJyZWxsaTwvaT4gdXNpbmcgaGVhZCBsZW5ndGgiKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQpGcm9tIHRoaXMsIGl0IGlzIHBvc3NpYmxlIHRvIHByb2R1Y2UgYSB2ZXJ5IGNydWRlIGVzdGltYXRlIHN1Z2dlc3RpbmcgdGhhdCBhZHVsdCBpbmRpdmlkdWFscyBvZiAqRC4gdGVycmVsbGkqIChpLmUuLCBDTU5IIDU3NjgpIGhhdmUgYW4gZXN0aW1hdGVkIGxlbmd0aCBvZiAyLjcgbSwgd2hpY2ggYmFzZWQgb24gdGhlIHByb3BvcnRpb25zIHNlZW4gaW4gZXh0YW50IGZpc2hlcyBzdWdnZXN0IGEgcG90ZW50aWFsIHJhbmdlIG9mIGJvZHkgbGVuZ3RoIGZyb20gMS45Ni00LjM2IG0uDQoNCmBgYHtyLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KZm9zc2lsX3RheGElPiUNCiAgZmlsdGVyKGxlbmd0aF9hcyE9ImVzdGltYXRlZCB0LmwuIixjbGFkZT09IlBsYWNvZGVybWkiKSU+JQ0KICBtdXRhdGUoY3J1ZGVfdG90YWxfbGVuZ3RoPWhlYWRfbGVuZ3RoL21lYW5faGVhZF9sZW5ndGgsDQogICAgICAgICBjcnVkZV90b3RhbF9sZW5ndGhfbG93ZXI9aGVhZF9sZW5ndGgvdXBwZXJfaGVhZF9sZW5ndGgsDQogICAgICAgICBjcnVkZV90b3RhbF9sZW5ndGhfdXBwZXI9aGVhZF9sZW5ndGgvbG93ZXJfaGVhZF9sZW5ndGgsDQogICAgICAgICBQRT0oKChjcnVkZV90b3RhbF9sZW5ndGgtdG90YWxfbGVuZ3RoKS9jcnVkZV90b3RhbF9sZW5ndGgpKSoxMDAsDQogICAgICAgICB0YXhvbj1zdHJfcmVwbGFjZSh0YXhvbiwiXyIsIiAiKSklPiUNCiAgcm93bmFtZXNfdG9fY29sdW1uKCklPiUNCiAgIyBUaGUgdHdvIGFyZSByZXZlcnNlZCBoZXJlIGJlY2F1c2UgYSBzbWFsbGVyICUgaGVhZCBsZW5ndGggcmVzdWx0cyBpbiBhIGxvbmdlciBib2R5DQogIGFycmFuZ2UoaGVhZF9sZW5ndGgpJT4lDQogIHNlbGVjdCh0YXhvbixzcGVjaW1lbixoZWFkX2xlbmd0aCxjcnVkZV90b3RhbF9sZW5ndGgsDQogICAgICAgICBjcnVkZV90b3RhbF9sZW5ndGhfbG93ZXIsY3J1ZGVfdG90YWxfbGVuZ3RoX3VwcGVyLHRvdGFsX2xlbmd0aCxQRSklPiUNCiAga2FibGUoZGlnaXRzPTEsYWxpZ249YygibCIsImMiLCJjIiwiYyIsImMiLCJjIiwiYyIsImMiKSwNCiAgICAgICAgY2FwdGlvbj0iRXN0aW1hdGUgb2YgdG90YWwgbGVuZ3RoIGluIHNwZWNpbWVucyBvZiBjb21wbGV0ZSBwbGFjb2Rlcm1zIHVzaW5nIGhlYWQgbGVuZ3RoIiwNCiAgICAgICAgY29sLm5hbWVzID0gYygiVGF4b24iLCJTcGVjaW1lbiIsIkhlYWQgTGVuZ3RoIiwiRXN0LiBMZW5ndGgiLCJMb3dlciIsIlVwcGVyIiwiQWN0dWFsIExlbmd0aCIsIiVQRSIpKSU+JQ0KICBjb2x1bW5fc3BlYygxLCBpdGFsaWMgPSBUKSU+JQ0KICBjb2x1bW5fc3BlYyhjKDQsNyksIGJvbGQgPSBUKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQpIb3dldmVyLCBpbiBjb21wbGV0ZSBzcGVjaW1lbnMgb2YgYXJ0aHJvZGlyZXMsIHVzaW5nIGhlYWQgbGVuZ3RoIHRvIGVzdGltYXRlIHRvdGFsIGxlbmd0aCBwcm9kdWNlcyBzeXN0ZW1hdGljIHVuZGVyZXN0aW1hdGVzIG9mIGJvZHkgbGVuZ3RoLCB3aGljaCBhcmUgcXVpdGUgc3Vic3RhbnRpYWwgaW4gc29tZSBjYXNlcy4NCg0KVGhlIDk1JSBwcmVkaWN0aW9uIGludGVydmFsIGZvciBoZWFkIGxlbmd0aCBjaXRlZCBpbiBUYWJsZSA0LjEgY29tcGxldGVseSBleGNsdWRlcyB0aGUgbGFyZ2VyIGJvZHkgc2l6ZXMgZm9yICpEdW5rbGVvc3RldXMqIHR5cGljYWxseSBjaXRlZCBpbiBwcmV2aW91cyBzdHVkaWVzLCB3aGljaCBhcmUgYWxtb3N0IGludmFyaWFibGUgZ3JlYXRlciB0aGFuIDUgbSBpbiBsZW5ndGggZm9yIGFkdWx0IGluZGl2aWR1YWxzIGxpa2UgQ01OSCA1NzY4LiBFdmVuIHRob3VnaCBoZWFkIGxlbmd0aCBwcm9kdWNlcyB1bmRlcmVzdGltYXRlcyBvZiBib2R5IHNpemUgaW4gYXJ0aHJvZGlyZXMsIHRoZXJlIGlzIGVzc2VudGlhbGx5IG5vIHdheSB0byBwcm9kdWNlIHNpemVzIGxhcmdlciB0aGFuIDUgbSBldmVuIGFzc3VtaW5nIGhlYWQtYm9keSBwcm9wb3J0aW9ucyBbY29tcGFyYWJsZSB0byAqQW1hemljaHRoeXMqXSgjQW1hemljaHRoeXMpICh3aGljaCBwcm9kdWNlcyBhbiBlc3RpbWF0ZWQgbGVuZ3RoIG9mIDQuMDUgbSBmb3IgQ01OSCA1NzY4KS4NCg0KTm90YWJseSwgdGhlIGFib3ZlIHJlc3VsdHMgbWFrZSBhYnNvbHV0ZWx5IG5vIGFkZGl0aW9uYWwgYXNzdW1wdGlvbnMgYXMgdG8gYW55IG90aGVyIGJpb2xvZ2ljYWwgZmVhdHVyZXMgb2YgdGhlIHRheGEgYmVpbmcgY29uc2lkZXJlZCwgc3VjaCBhcyB0aGUgW3Nob3J0ZXIgc25vdXRzIG9mIGFydGhyb2RpcmVzIGNvbXBhcmVkIHRvIG90aGVyIGZpc2hlc10oI3Nub3V0bGVuZ3RoKSwgdGhlIGZhY3QgdGhhdCB0aGUgbW9zdCBleHRyZW1lIGhlYWQgcHJvcG9ydGlvbnMgaW4gdGhpcyBkYXRhc2V0IGFyZSBzZWVuIGluIHN0cm9uZ2x5IGFuZ3VpbGxpZm9ybSBvciBjb21wcmVzc2VkIGJvZHkgc2hhcGVzIHRoYXQgYXJlIG5vdCBsaWtlbHkgdG8gYmUgcHJlc2VudCBpbiAqRHVua2xlb3N0ZXVzKiwgZXRjLiBUaHVzLCB0aGVzZSBlc3RpbWF0ZXMgY2FuIGJlIGZ1cnRoZXIgcmVmaW5lZCwgYXMgc2VlbiBiZWxvdy4uLg0KDQojIEFsbCB0YXhvbiBtb2RlbA0KDQojIyBHcmFwaCBvZiBPT0wgYWdhaW5zdCB0b3RhbCBsZW5ndGgNCg0KKHJlZjpPT0xncmFwaDEpIFBsb3Qgb2Ygb3JiaXQtb3BlcmN1bGFyIGxlbmd0aCBhZ2FpbnN0IHRvdGFsIGxlbmd0aCBpbiBmaXNoZXMgKHVzaW5nIGEgbG9nPHN1Yj4xMDwvc3ViPiBzY2FsZSksIHVzaW5nIGluZGl2aWR1YWwgc3BlY2ltZW4gdmFsdWVzLg0KDQpgYGB7cixmaWcud2lkdGg9NyxmaWcuYXNwPTAuNzUsd2FybmluZz1GLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnLGZpZy5jYXA9IihyZWY6T09MZ3JhcGgxKSJ9DQpmaXQuT09MPC1sbShsb2codG90YWxfbGVuZ3RoKX5sb2coT09MKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhX2ZpbmFsICU+JSBmaWx0ZXIobGVuZ3RoX2FzIT0iZXN0aW1hdGVkIHQubC4iKSkNCihkdW5rX2xlbmd0aF9zcGVjaW1lbnM8LWdncGxvdCgNCiAgZGF0YV9maW5hbCU+JQ0KICAgIGZpbHRlcihsZW5ndGhfYXM9PSJ0b3RhbCBsZW5ndGgiKSU+JQ0KICAgIGRyb3BfbmEoT09MLHRvdGFsX2xlbmd0aCklPiUNCiAgICBhdWdtZW50KGZpdC5PT0wsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0aW9uIiklPiUNCiAgICBtdXRhdGUoYWNyb3NzKC5maXR0ZWQ6LnVwcGVyLH5leHAoLikqcmVncmVzc2lvbi5zdGF0cyhmaXQuT09MKSRDRikpJT4lDQogICAgYXJyYW5nZShjbGFkZSksDQogIGFlcyh5PXRvdGFsX2xlbmd0aCx4PU9PTCkpKw0KICBnZW9tX3Ntb290aChhZXMoY29sb3I9Y2xhZGUpLHNlPUYsbWV0aG9kPSJsbSIsZm9ybXVsYT15fngpKw0KICBnZW9tX3N0YXIoYWVzKGZpbGw9Y2xhZGUsc3RhcnNoYXBlPWNsYWRlKSxzaXplPTIpKw0KICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIixmb3JtdWxhPXl+eCkrDQogIGdlb21fbGluZShhZXMoeT0ubG93ZXIpLCBjb2xvciA9ICJibHVlIiwgbGluZXR5cGUgPSAiZGFzaGVkIikrDQogIGdlb21fbGluZShhZXMoeT0udXBwZXIpLCBjb2xvciA9ICJibHVlIiwgbGluZXR5cGUgPSAiZGFzaGVkIikrDQogIGdlb21fc21vb3RoKGRhdGE9LiU+JWZpbHRlcihjbGFkZT09IkNob25kcmljaHRoeWVzIiksDQogICAgICAgICAgICAgIG1ldGhvZD0ibG0iLGNvbD0iZ3JlZW4iLGZvcm11bGE9eX54KSsNCiAgZ2VvbV9zdGFyKGFlcyhmaWxsPWNsYWRlLHN0YXJzaGFwZT1jbGFkZSksY29sb3I9IndoaXRlIixzdGFyc3Ryb2tlPTAuMzMsDQogICAgICAgICAgICAuJT4lZmlsdGVyKGNsYWRlPT0iUGxhY29kZXJtaSIpLHNpemU9MyxzaG93LmxlZ2VuZD1GKSsNCiAgc2NhbGVfc3RhcnNoYXBlX21hbnVhbCh2YWx1ZXM9YygxNSwxMywxMSwxLDI4KSkrDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKGh1ZV9wYWwoKSg0KVsxOjNdLCJibGFjayIsaHVlX3BhbCgpKDQpWzRdKSxuYS52YWx1ZT1OQSkrDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YyhodWVfcGFsKCkoNClbMToyXSxOQSxOQSwiTkEiKSxuYS52YWx1ZT1OQSkrDQogIGxhYnMoeD0iT3JiaXQtT3BlcmN1bGFyIExlbmd0aCAoY20pIix5PSJUb3RhbCBMZW5ndGggKGNtKSIsDQogICAgICAgY29sb3I9IkNsYWRlIixzdGFyc2hhcGU9IkNsYWRlIixmaWxsPSJDbGFkZSIpKw0KICBzY2FsZV94X2NvbnRpbnVvdXModHJhbnM9J2xvZzEwJykrDQogIHNjYWxlX3lfY29udGludW91cyh0cmFucz0nbG9nMTAnKSsNCiAgZ2VvbV90ZXh0KGFlcyh4ID0gMC4xOCwgeSA9IDE5MDAsIGxhYmVsID0gbG1fZXFuLmFsbChmaXQuT09MKSksDQogICAgICAgICAgICBoanVzdD0wLCBwYXJzZSA9IFRSVUUsZGF0YS5mcmFtZSgpKSsNCiAgdGhlbWVfY2xhc3NpYygpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249YygwLjg1LDAuMikpKQ0KZ2dzYXZlKCJGaWd1cmUgNyAoT09MIFJlZ3Jlc3Npb24pLnRpZmYiLGR1bmtfbGVuZ3RoX3NwZWNpbWVucyxkZXZpY2U9InRpZmYiLA0KICAgICAgIHdpZHRoPTE3MCxoZWlnaHQ9MTI3LjUsZHBpPTYwMCx1bml0cz0ibW0iLGNvbXByZXNzaW9uPSJsenciKQ0KYGBgDQoNCiMjIFJlc3VsdHMgb2YgbW9kZWwNCg0KYGBge3J9DQpPT0xfcmVzaWR1YWxzPC1kYXRhX2ZpbmFsJT4lDQogIGZpbHRlcighaXMubmEoT09MKSwhaXMubmEodG90YWxfbGVuZ3RoKSxsZW5ndGhfYXMhPSJlc3RpbWF0ZWQgdC5sLiIpJT4lDQogIG11dGF0ZShyZXNpZHVhbHM9KHJlc2lkdWFscyhsbShsb2codG90YWxfbGVuZ3RoKX5sb2coT09MKSkpKSkNCnN1bW1hcnkoZml0Lk9PTCkNCnJlZ3Jlc3Npb24uc3RhdHMoZml0Lk9PTCkNCmBgYA0KDQojIyBEaWFnbm9zdGljIHBsb3RzIGZvciBtb2RlbA0KDQpgYGB7cixmaWcud2lkdGg9MTAsZmlnLmhlaWdodD03LjUsZmlnLmNhcD0iRGlhZ25vc3RpYyBwbG90cyBmb3IgbW9kZWwgcmVncmVzc2luZyB0b3RhbCBsZW5ndGggYWdhaW5zdCBvcmJpdC1vcGVyY3VsYXIgbGVuZ3RoIn0NCnBhcihtZnJvdz1jKDIsMikpDQpwbG90KGZpdC5PT0wpDQpgYGANCg0KIyMgQ29tcGFyaW5nIHRvIHVudHJhbnNmb3JtZWQgbW9kZWwNCg0KIyMjIE1vZGVsIHN1bW1hcnkNCg0KYGBge3J9DQpsbSh0b3RhbF9sZW5ndGh+T09MLGRhdGFfZmluYWwpICU+JQ0KICBzdW1tYXJ5KCkNCmBgYA0KDQojIyMgR3JhcGggb2YgdW50cmFuc2Zvcm1lZCBPT0wgdmVyc3VzIHRvdGFsIGxlbmd0aA0KDQoocmVmOnVudHJhbnNmb3JtZWQpIFBsb3Qgb2YgT09MIHZlcnN1cyB0b3RhbCBsZW5ndGggdXNpbmcgdW50cmFuc2Zvcm1lZCBkYXRhLiBOb3RlIHRoYXQgdGhlIHJlZ3Jlc3Npb24gbGluZSBpcyBjYWxjdWxhdGVkIG9uIHRoZSBkYXRhIHdpdGhvdXQgbG9nLXRyYW5zZm9ybWF0aW9uLCB0aGlzIGlzIG5vdCB0aGUgcmVncmVzc2lvbiBsaW5lIG9mIHRoZSBsb2ctdHJhbnNmb3JtZWQgZGF0YSBiYWNrLXRyYW5zZm9ybWVkIHRvIGFyaXRobWV0aWMgdW5pdHMuDQoNCmBgYHtyLGZpZy5jYXA9IihyZWY6dW50cmFuc2Zvcm1lZCkifQ0KZ2dwbG90KA0KICBkYXRhX2ZpbmFsJT4lDQogICAgZmlsdGVyKGxlbmd0aF9hcz09InRvdGFsIGxlbmd0aCIpJT4lDQogICAgZHJvcF9uYShPT0wsdG90YWxfbGVuZ3RoKSU+JQ0KICAgIGF1Z21lbnQobG0odG90YWxfbGVuZ3Rofk9PTCxkYXRhX2ZpbmFsKSxuZXdkYXRhPS4saW50ZXJ2YWw9InByZWRpY3Rpb24iKSU+JQ0KICAgIGFycmFuZ2UoY2xhZGUpLA0KICBhZXMoeT10b3RhbF9sZW5ndGgseD1PT0wpKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKGNvbG9yPWNsYWRlKSxzZT1GLG1ldGhvZD0ibG0iLGZvcm11bGE9eX54KSsNCiAgZ2VvbV9zdGFyKGFlcyhmaWxsPWNsYWRlLHN0YXJzaGFwZT1jbGFkZSksc2l6ZT0yKSsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSJsbSIsZm9ybXVsYT15fngpKw0KICBnZW9tX2xpbmUoYWVzKHk9Lmxvd2VyKSwgY29sb3IgPSAiYmx1ZSIsIGxpbmV0eXBlID0gImRhc2hlZCIpKw0KICBnZW9tX2xpbmUoYWVzKHk9LnVwcGVyKSwgY29sb3IgPSAiYmx1ZSIsIGxpbmV0eXBlID0gImRhc2hlZCIpKw0KICBnZW9tX3Ntb290aChkYXRhPS4lPiVmaWx0ZXIoY2xhZGU9PSJDaG9uZHJpY2h0aHllcyIpLA0KICAgICAgICAgICAgICBtZXRob2Q9ImxtIixjb2w9ImdyZWVuIixmb3JtdWxhPXl+eCkrDQogIGdlb21fc3RhcihhZXMoZmlsbD1jbGFkZSxzdGFyc2hhcGU9Y2xhZGUpLGNvbG9yPSJ3aGl0ZSIsc3RhcnN0cm9rZT0wLjMzLA0KICAgICAgICAgICAgLiU+JWZpbHRlcihjbGFkZT09IlBsYWNvZGVybWkiKSxzaXplPTMsc2hvdy5sZWdlbmQ9RikrDQogIHNjYWxlX3N0YXJzaGFwZV9tYW51YWwodmFsdWVzPWMoMTUsMTMsMTEsMSwyOCkpKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YyhodWVfcGFsKCkoNClbMTozXSwiYmxhY2siLGh1ZV9wYWwoKSg0KVs0XSksbmEudmFsdWU9TkEpKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoaHVlX3BhbCgpKDQpWzE6Ml0sTkEsTkEsIk5BIiksbmEudmFsdWU9TkEpKw0KICBsYWJzKHg9Ik9yYml0LU9wZXJjdWxhciBMZW5ndGggKGNtKSIseT0iVG90YWwgTGVuZ3RoIChjbSkiLA0KICAgICAgIGNvbG9yPSJDbGFkZSIsc3RhcnNoYXBlPSJDbGFkZSIsZmlsbD0iQ2xhZGUiKSsNCiAgZ2VvbV90ZXh0KGFlcyh4ID0gMC4xOCwgeSA9IDk1MCwNCiAgICAgICAgICAgICAgICBsYWJlbCA9IGxtX2Vxbi5hbGwobG0odG90YWxfbGVuZ3Rofk9PTCxkYXRhX2ZpbmFsKSkpLA0KICAgICAgICAgICAgaGp1c3Q9MCwgcGFyc2UgPSBUUlVFLGRhdGEuZnJhbWUoKSkrDQogIHRoZW1lX2NsYXNzaWMoKSsNCiAgY29vcmRfY2FydGVzaWFuKHk9YygtNTAsMTAwMCkpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249YygwLjg1LDAuMikpDQpgYGANCg0KIyMjIERpYWdub3N0aWMgcGxvdHMNCg0KYGBge3IsLGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTcuNSxmaWcuY2FwPSJEaWFnbm9zdGljIHBsb3RzIGZvciB1bnRyYW5zZm9ybWVkIG1vZGVsIn0NCnBhcihtZnJvdz1jKDIsMikpDQpwbG90KGxtKHRvdGFsX2xlbmd0aH5PT0wsZGF0YV9maW5hbCkpDQpgYGANCg0KIyMjIEhpc3RvZ3JhbSBvZiByZXNpZHVhbHMNCg0KYGBge3IsZmlnLmFzcD0wLjUsZmlnLmNhcD0iSGlzdG9ncmFtIG9mIHJlc2lkdWFscyBpbiB1bnRyYW5zZm9ybWVkIGFuZCBuYXR1cmFsIGxvZyB0cmFuc2Zvcm1lZCBtb2RlbCJ9DQpncmlkLmFycmFuZ2UobmNvbD0yLA0KIGdncGxvdChkYXRhX2ZpbmFsJT4lZmlsdGVyKCFpcy5uYShPT0wpKSU+JQ0KICAgICAgICAgIGZpbHRlcighaXMubmEodG90YWxfbGVuZ3RoKSksDQogICAgICAgIGFlcyh4PXJlc2lkdWFscyhsbSh0b3RhbF9sZW5ndGh+T09MKSkpKSsNCiAgIGdlb21faGlzdG9ncmFtKGNvbD0iYmxhY2siLGZpbGw9ImdyYXkiLGJpbnM9MzApKw0KICAgc3RhdF9mdW5jdGlvbihmdW4gPSBmdW5jdGlvbih4KSBkbm9ybSh4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZWFuPW1lYW4ocmVzaWR1YWxzKGxtKHRvdGFsX2xlbmd0aH5PT0wsZGF0YV9maW5hbCksbmEucm09VFJVRSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZD1zZChyZXNpZHVhbHMobG0odG90YWxfbGVuZ3Rofk9PTCxkYXRhX2ZpbmFsKSxuYS5ybT1UUlVFKSkpKg0KICAgICAgICAgICAgICAgICAgICAgICAgbnJvdyhkYXRhLmZyYW1lKHJlc2lkdWFscyhsbSh0b3RhbF9sZW5ndGh+T09MLGRhdGFfZmluYWwpLG5hLnJtPVRSVUUpKSkqDQogICAgICAgICAgICAgICAgICAgICAgICAoKG1heChyZXNpZHVhbHMobG0odG90YWxfbGVuZ3Rofk9PTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhX2ZpbmFsKSkpKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgYWJzKG1pbihyZXNpZHVhbHMobG0odG90YWxfbGVuZ3Rofk9PTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfZmluYWwpKSkpKS8NCiAgICAgICAgICAgICAgICAgICAgICAgICAgIDMwKSwNCiAgICAgICAgICAgICAgICBjb2xvciA9ICJyZWQiLCBzaXplID0gMC41KSsNCiAgIGdndGl0bGUoIlVudHJhbnNmb3JtZWQgTW9kZWwiKSsNCiAgIGxhYnMoeT0iQ291bnQiLHg9IlJlc2lkdWFscyIpK3RoZW1lX2NsYXNzaWMoKSwNCiBnZ3Bsb3QoZGF0YV9maW5hbCU+JWZpbHRlcighaXMubmEoT09MKSklPiUNCiAgICAgICAgICBmaWx0ZXIoIWlzLm5hKHRvdGFsX2xlbmd0aCkpLA0KICAgICAgICBhZXMoeD1yZXNpZHVhbHMobG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCkpKSkpKw0KICAgZ2VvbV9oaXN0b2dyYW0oY29sPSJibGFjayIsZmlsbD0iZ3JheSIsYmlucz0zMCkrDQogICBzdGF0X2Z1bmN0aW9uKGZ1biA9IGZ1bmN0aW9uKHgpIGRub3JtKHgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lYW49bWVhbihyZXNpZHVhbHMobG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCksZGF0YV9maW5hbCksbmEucm09VFJVRSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZD1zZChyZXNpZHVhbHMobG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCksZGF0YV9maW5hbCksbmEucm09VFJVRSkpKSoNCiAgICAgICAgICAgICAgICAgICAgICAgIG5yb3coZGF0YS5mcmFtZShyZXNpZHVhbHMobG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCksZGF0YV9maW5hbCksbmEucm09VFJVRSkpKSoNCiAgICAgICAgICAgICAgICAgICAgICAgICgobWF4KHJlc2lkdWFscyhsbShsb2codG90YWxfbGVuZ3RoKX4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvZyhPT0wpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfZmluYWwpKSkrDQogICAgICAgICAgICAgICAgICAgICAgICAgICBhYnMobWluKHJlc2lkdWFscyhsbShsb2codG90YWxfbGVuZ3RoKX4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbG9nKE9PTCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhX2ZpbmFsKSkpKSkvDQogICAgICAgICAgICAgICAgICAgICAgICAgICAzMCksDQogICAgICAgICAgICAgICAgY29sb3IgPSAicmVkIiwgc2l6ZSA9IDAuNSkrDQogICBnZ3RpdGxlKCJMb2ctVHJhbnNmb3JtZWQgTW9kZWwiKSsNCiAgIGxhYnMoeT0iQ291bnQiLHg9IlJlc2lkdWFscyIpK3RoZW1lX2NsYXNzaWMoKQ0KKQ0KYGBgDQoNCkJhc2VkIG9uIHRoZSBoZXRlcm9za2V0YXN0aWNpdHkgaW4gdGhlIHNjYWxlLWxvY2F0aW9uIHBsb3QsIGluY3JlYXNlZCBsZXZlcmFnZSBvZiBjZXJ0YWluIGhpZ2hseSBpbmZsdWVudGlhbCBwb2ludHMgKG1vc3RseSBsYXJnZSB0YXhhKSwgYW5kIHRoZSBzdHJvbmdseSBsZXB0b2t1cnRpYyBkaXN0cmlidXRpb24gb2YgdGhlIHJlc2lkdWFscyBvZiB0aGUgdW50cmFuc2Zvcm1lZCBtb2RlbCwgYSBtb2RlbCBpbiB3aGljaCB0aGUgZGF0YSBpcyBsb2ctdHJhbnNmb3JtZWQgYmVmb3JlIGFuYWx5c2lzIGlzIHByZWZlcnJlZCBoZXJlLg0KDQojIyMgRXhhbWluYXRpb24gb2Yga3VydG9zaXMNCg0KYGBge3J9DQpyYmluZCgiVW50cmFuc2Zvcm1lZCBNb2RlbCI9ZGF0YV9maW5hbCUkJQ0KICBsbSh0b3RhbF9sZW5ndGh+T09MKSU+JQ0KICByZXNpZHVhbHMoKSU+JQ0KICBrdXJ0b3NpcygpLA0KIkxvZy1UcmFuc2Zvcm1lZCBNb2RlbCI9Zml0Lk9PTCU+JQ0KICByZXNpZHVhbHMoKSU+JQ0KICBrdXJ0b3NpcygpLA0KIkJhY2stVHJhbnNmb3JtZWQgUmVzaWR1YWxzIj1maXQuT09MJT4lDQogIHJlc2lkdWFscygpJT4lDQogIGV4cCgpJT4lDQogIGt1cnRvc2lzKCkNCiklPiVkYXRhLmZyYW1lKCklPiUNCiAgYWRkX2NvbHVtbihza2V3bmVzcz1jKHNrZXduZXNzKHJlc2lkdWFscyhsbSh0b3RhbF9sZW5ndGh+T09MLGRhdGFfZmluYWwpKSksDQogICAgICAgICAgICAgICAgICAgICAgICBza2V3bmVzcyhyZXNpZHVhbHMoZml0Lk9PTCkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgc2tld25lc3MoZXhwKHJlc2lkdWFscyhmaXQuT09MKSkpKSklPiUNCiAga2FibGUoZGlnaXRzPTIsYWxpZ249ImMiLGNvbC5uYW1lcz1jKCJFeGNlc3MgS3VydG9zaXMiLCJTa2V3bmVzcyIpLA0KICAgICAgICAgIGNhcHRpb249Ikt1cnRvc2lzIGFuZCBza2V3bmVzcyBmb3IgdGhlIHJlZ3Jlc3Npb24gbW9kZWwgYmV0d2VlbiBPT0wgd2l0aCBhbmQgd2l0aG91dCBsb2ctdHJhbnNmb3JtYXRpb24sIGFzIHdlbGwgYXMgdGhlIHJlc2lkdWFscyBmcm9tIGEgbG9nLXRyYW5zZm9ybWVkIG1vZGVsIGJhY2stdHJhbnNmb3JtZWQgdG8gYW4gYXJpdGhtZXRpYyBzY2FsZS4iKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQpCYXNlZCBvbiB0aGlzLCB0aGUgbG9nLXRyYW5zZm9ybWVkIG1vZGVsIHNob3dzIG1pbm9yIGV4Y2VzcyBrdXJ0b3NpcyAoa3VydG9zdXMgPCAzKSwgYW5kIGxpdHRsZSBza2V3bmVzcy4gSG93ZXZlciwgd2hlbiBiYWNrLXRyYW5zZm9ybWVkIGludG8gYW4gYXJpdGhtZXRpYyBzY2FsZSwgdGhlIHJlc2lkdWFscyBvZiB0aGUgbW9kZWwgc2hvdyBleHRyZW1lIGt1cnRvc2lzICg+IDMwKS4gVGhpcyBtZWFucyB0aGF0IHdoaWxlIGxvZy10cmFuc2Zvcm1hdGlvbiBub3JtYWxpemVzIHRoZSBkaXN0cmlidXRpb24gb2YgZGF0YSBhbmQgbWFrZXMgaXQgcG9zc2libGUgdG8gYWNjdXJhdGVseSBmaXQgYSByZWdyZXNzaW9uIG1vZGVsLCB3aGVuIGJhY2stdHJhbnNmb3JtZWQgaW50byBhcml0aG1ldGljIHRlcm1zIGl0IGZhaWxzIHRvIGFjY3VyYXRlbHkgcHJvamVjdCBwcmVkaWN0aW9uIGludGVydmFscyBmb3IgbmV3IGRhdGEsIHNpbmNlIHRoZSBwcmVkaWN0aW9uIGludGVydmFscyBhcmUgYmFzZWQgaW4gcGFydCBvbiB0aGUgc3RhbmRhcmQgZXJyb3Igb2YgdGhlIG1vZGVsIChhbmQgaGVuY2UgdGhlIHJlc2lkdWFscykuDQoNCk9yLCB0byBwdXQgaXQgaW4gbGVzcyBzdGF0aXN0aWNhbGx5IG1pbmRlZCB0ZXJtcywgZGUtdHJhbnNmb3JtaW5nIGEgbG9nLWxvZyBtb2RlbCByZXN1bHRzIG91dGxpZXJzIGJlaW5nIGV4YWdnZXJhdGVkLCBiZWNhdXNlIGVycm9yIGdvZXMgZnJvbSBiZWluZyBjYWxjdWxhdGVkIG9uIGEgbG9nLXNjYWxlIHRvIGFuIHVubG9nZ2VkIG9uZS4gVGhpcyByZXN1bHRzIGluIG91dGxpZXJzIGhhdmluZyBhIGRpc3Byb3BvcnRpb25hdGUgZWZmZWN0IG9uIHRoZSBtb2RlbC4gRGV0cmFuc2Zvcm1hdGlvbiB3aWxsIHRyYW5zZm9ybSBhbG1vc3QgYW55IHN0dWR5J3MgZGlzdHJpYnV0aW9uIG9mIHJlc2lkdWFscyBmcm9tIGEgbmVhcmx5LW5vcm1hbCBkaXN0cmlidXRpb24gdG8gYSBsZXB0b2t1cnRpYyBvbmUsIHVubGVzcyB0aGUgbW9kZWwgaXRzZWxmIGlzIGFsbW9zdCBwZXJmZWN0bHkgbm9ybWFsICh3aGljaCBpdCBuZXZlciBpcykuIENhbGN1bGF0aW5nIHByZWRpY3Rpb24gaW50ZXJ2YWxzIG9uIGxlcHRva3VydGljIG1vZGVscyBpcyBrbm93biB0byBkcmFzdGljYWxseSBpbmZsYXRlIGVycm9yIGJvdW5kcyAoW01pbGxlciwgMTk4Nl0oI3JlZmVyZW5jZXMpKSwgaG93ZXZlciB0aGVyZSBhcmUgZmV3IHN1Z2dlc3Rpb25zIHRvIGhvdyB0byBhcHBseSB0aGlzIHRvIGxvZy10cmFuc2Zvcm1lZCByZWdyZXNzaW9uIGVxdWF0aW9ucywgc3VjaCBhcyB0aG9zZSBvZiBtb3N0IGJpb2xvZ2ljYWwgZGF0YS4NCg0KIyMjIEVzdGltYXRlcyBvZiBwbGFjb2Rlcm1zIHVzaW5nIGFuIHVudHJhbnNmb3JtZWQgbW9kZWwNCg0KYGBge3J9DQpmb3NzaWxfdGF4YSU+JQ0KICBmaWx0ZXIoISFmb3NzaWwuc3BlY2ltZW5zKSU+JQ0KICBhdWdtZW50KGxtKHRvdGFsX2xlbmd0aH5PT0wsZGF0YV9maW5hbCksbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0IiklPiUNCiAgc2VsZWN0KHRheG9uLHNwZWNpbWVuLHRvdGFsX2xlbmd0aCwuZml0dGVkLC5sb3dlciwudXBwZXIpJT4lDQogIGthYmxlKGRpZ2l0cz0xLGFsaWduPWMoImwiLCJjIiwiYyIsImMiLCJjIiwiYyIpLA0KICAgICAgICBjb2wubmFtZXMgPSBjKCJUYXhvbiIsIlNwZWNpbWVuIiwiQWN0dWFsIExlbmd0aCIsIkVzdC4iLCJMb3dlciA5NSUgUC5JLiIsIlVwcGVyIDk1JSBQLkkuIiksDQogICAgICAgIGNhcHRpb249Ikxlbmd0aCBlc3RpbWF0ZXMgZm9yIHBsYWNvZGVybXMga25vd24gZnJvbSBjb21wbGV0ZSBib2R5IGZvc3NpbHMgdXNpbmcgYSBub24tbG9nIHRyYW5zZm9ybWVkIG1vZGVsLiIpJT4lDQogIGNvbHVtbl9zcGVjKDEsaXRhbGljPVQpJT4lDQogIGFkZF9oZWFkZXJfYWJvdmUoYygiICI9MywiRXN0aW1hdGVkIExlbmd0aCI9MykpJT4lDQogIGNvbHVtbl9zcGVjKDQsYm9sZD1UKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQpOb3RlIGhvdyBmb3IgdGhlIHZlcnkgc21hbGxlc3QgcGxhY29kZXJtIHRheGEgKGUuZy4sICpNaWxsZXJvc3RldXMqLCAqQ29jY29zdGV1cyopIHRoZSA5NSUgcHJlZGljdGlvbiBpbnRlcnZhbHMgZm9yIHRoZSBtb2RlbCB3aGVyZSB0aGUgZGF0YSBpcyBub3QgbG9nLXRyYW5zZm9ybWVkIGJlZm9yZSByZWdyZXNzaW5nIGFyZSBuZWdhdGl2ZS4gVGhpcyBpcyBsaWtlbHkgYSByZXN1bHQgb2YgaGV0ZXJvc2tlZGFzdGljaXR5IGluIHRoZSBtb2RlbCAoaS5lLiwgbGFyZ2VyIGZpc2hlcyBhcmUgbWVhc3VyZWQgd2l0aCBtb3JlIGVycm9yKSwgd2hpY2ggbWVhbnMgdGhhdCBhbiB1bnRyYW5zZm9ybWVkIG1vZGVsIGlzIHVuc3VpdGFibGUgZm9yIGVzdGltYXRpbmcgbGVuZ3RoIGluICpEdW5rbGVvc3RldXMqLg0KDQojIyBEaXN0cmlidXRpb24gb2YgcmVzaWR1YWxzDQoNCihyZWY6cmVzaWR1YWxkaXN0cmlidXRpb24pIEJveC1hbmQtd2hpc2tlciBwbG90IG9mIGRpc3RyaWJ1dGlvbiBvZiByZXNpZHVhbHMgaW4gT09MIG1vZGVsIHNlcGFyYXRlZCBieSBjbGFkZS4NCg0KYGBge3IsZmlnLmNhcD0iKHJlZjpyZXNpZHVhbGRpc3RyaWJ1dGlvbikiLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KZ2dwbG90KE9PTF9yZXNpZHVhbHMlPiVmaWx0ZXIobGVuZ3RoX2FzIT0iZXN0aW1hdGVkIHQubC4iKSwNCiAgICAgICBhZXMoeT1yZXNpZHVhbHMseD1jbGFkZSkpKw0KICBnZW9tX3Zpb2xpbihhZXMoZmlsbD1jbGFkZSksc2NhbGU9IndpZHRoIikrDQogIGdlb21faGxpbmUoeWludGVyY2VwdD0wLGxpbmV0eXBlPSJkYXNoZWQiKSsNCiAgZ2VvbV9obGluZShkYXRhPS4lPiVmaWx0ZXIoY2xhZGU9PSJQbGFjb2Rlcm1pIiksDQogICAgICAgICAgICAgYWVzKHlpbnRlcmNlcHQ9bWVhbihyZXNpZHVhbHMpKSxjb2xvcj0iIzAwQjBGNiIsbGluZXR5cGU9ImRhc2hlZCIpKw0KICBnZW9tX2JveHBsb3Qod2lkdGg9MC4zKSsNCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1zZXEoLTEuNSwxLjUsMC4yKSkrDQogIGxhYnMoeT0iUmVzaWR1YWxzIix4PSJDbGFkZSIpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQ0KYGBgDQoNCkFzIGNhbiBiZSBzZWVuLCBhcnRocm9kaXJlIHBsYWNvZGVybXMgc2hvdyBzbGlnaHRseSBuZWdhdGl2ZSByZXNpZHVhbHMgKGkuZS4sIGEgc2xpZ2h0IG92ZXJlc3RpbWF0ZSBvZiBib2R5IGxlbmd0aCksIGJ1dCBvdmVyYWxsIHRoZWlyIHJlc2lkdWFscyBhcmUgd2VsbCB3aXRoaW4gdGhlIHJhbmdlIG9mIHZhcmlhdGlvbiBvZiBvdGhlciBmaXNoZXMuDQoNCiMjIFNwZWNpZXMtYXZlcmFnZSBtb2RlbA0KDQoocmVmOnNwZWNpZXNhdmVyYWdlKSBQbG90IG9mIG9yYml0LW9wZXJjdWxhciBsZW5ndGggYWdhaW5zdCB0b3RhbCBsZW5ndGggaW4gZmlzaGVzIChvbiBhIGxvZzxzdWI+MTA8L3N1Yj4gc2NhbGUpLCB1c2luZyB0aGUgYXZlcmFnZSB2YWx1ZSBmb3IgYWR1bHRzIG9mIGVhY2ggc3BlY2llcy4NCg0KYGBge3IsZmlnLndpZHRoPTcsZmlnLmFzcD0wLjc1LHdhcm5pbmc9RixjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJyxmaWcuY2FwPSIocmVmOnNwZWNpZXNhdmVyYWdlKSJ9DQpmaXQuc3BlY2llc19hdmVyYWdlPC1sbShsb2codG90YWxfbGVuZ3RoKX5sb2coT09MKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfc3BlY2llcyU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sdW1uX3RvX3Jvd25hbWVzKCJ0YXhvbiIpKQ0KZml0LnNwZWNpZXNfYXZlcmFnZTI8LWxtKGxvZyh0b3RhbF9sZW5ndGgpfmxvZyhPT0wpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YV9zcGVjaWVzJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcighc2hhcGUgJWluJSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYygibWFjcnVyaWZvcm0iLCJjb21wcmVzc2lmb3JtIiwiYW5ndWlsaWZvcm0iKSU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXBsYWNlX25hKFRSVUUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIWZhbWlseSAlaW4lIGMoIkJhbGlzdGlkYWUiLCJNb25hY2FudGhpZGFlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJTZXJyYW5pZGFlIiwiSG9sb2NlbnRyaWRhZSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIWdlbnVzICVpbiUgYygiUmhpbm9jaGltYWVyYSIpKSU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2x1bW5fdG9fcm93bmFtZXMoInRheG9uIikpDQpkYXRhX3NwZWNpZXM8LWRhdGFfc3BlY2llcyU+JXVuZ3JvdXAoKSU+JQ0KICBtdXRhdGUoZml0PWV4cChwcmVkaWN0KGZpdC5zcGVjaWVzX2F2ZXJhZ2UsZGF0YV9zcGVjaWVzLGludGVydmFsPSJwcmVkaWN0aW9uIikpWywxXSwNCiAgICAgICAgIGx3cj1leHAocHJlZGljdChmaXQuc3BlY2llc19hdmVyYWdlLGRhdGFfc3BlY2llcyxpbnRlcnZhbD0icHJlZGljdGlvbiIpKVssMl0sDQogICAgICAgICB1cHI9ZXhwKHByZWRpY3QoZml0LnNwZWNpZXNfYXZlcmFnZSxkYXRhX3NwZWNpZXMsaW50ZXJ2YWw9InByZWRpY3Rpb24iKSlbLDNdLA0KICAgICAgICAgcmVzaWR1YWxzPXJlc2lkdWFscyhmaXQuc3BlY2llc19hdmVyYWdlMilbbWF0Y2goZGF0YV9zcGVjaWVzJHRheG9uLG5hbWVzKGZpdC5zcGVjaWVzX2F2ZXJhZ2UyJHJlc2lkdWFscykpXSkNCmdncGxvdCgNCiAgZGF0YV9zcGVjaWVzJT4lDQogICAgZHJvcF9uYShPT0wsdG90YWxfbGVuZ3RoKSU+JQ0KICAgIGF1Z21lbnQoZml0LnNwZWNpZXNfYXZlcmFnZSxuZXdkYXRhPS4saW50ZXJ2YWw9InByZWRpY3Rpb24iKSU+JQ0KICAgIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5zcGVjaWVzX2F2ZXJhZ2UpJENGKSklPiUNCiAgICBhcnJhbmdlKGNsYWRlKSwNCiAgYWVzKHk9dG90YWxfbGVuZ3RoLHg9T09MKSkrDQogIGdlb21fc21vb3RoKGFlcyhjb2xvcj1jbGFkZSksc2U9RixtZXRob2Q9ImxtIixmb3JtdWxhPXl+eCkrDQogIGdlb21fc3RhcihhZXMoZmlsbD1jbGFkZSxzdGFyc2hhcGU9Y2xhZGUpLHNpemU9MikrDQogIGdlb21fc21vb3RoKG1ldGhvZD0ibG0iLGZvcm11bGE9eX54KSsNCiAgZ2VvbV9saW5lKGFlcyh5PS5sb3dlciksIGNvbG9yID0gImJsdWUiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSsNCiAgZ2VvbV9saW5lKGFlcyh5PS51cHBlciksIGNvbG9yID0gImJsdWUiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSsNCiAgZ2VvbV9zbW9vdGgoZGF0YT0uJT4lZmlsdGVyKGNsYWRlPT0iQ2hvbmRyaWNodGh5ZXMiKSwNCiAgICAgICAgICAgICAgbWV0aG9kPSJsbSIsY29sPSJncmVlbiIsZm9ybXVsYT15fngpKw0KICBnZW9tX3N0YXIoYWVzKGZpbGw9Y2xhZGUsc3RhcnNoYXBlPWNsYWRlKSxjb2xvcj0id2hpdGUiLHN0YXJzdHJva2U9MC4zMywNCiAgICAgICAgICAgIC4lPiVmaWx0ZXIoY2xhZGU9PSJQbGFjb2Rlcm1pIiksc2l6ZT0zLHNob3cubGVnZW5kPUYpKw0KICBzY2FsZV9zdGFyc2hhcGVfbWFudWFsKHZhbHVlcz1jKDE1LDEzLDExLDEsMjgpKSsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoaHVlX3BhbCgpKDQpWzE6M10sImJsYWNrIixodWVfcGFsKCkoNClbNF0pLG5hLnZhbHVlPU5BKSsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKGh1ZV9wYWwoKSg0KVsxOjJdLE5BLE5BLCJOQSIpLG5hLnZhbHVlPU5BKSsNCiAgbGFicyh4PSJPcmJpdC1PcGVyY3VsYXIgTGVuZ3RoIChjbSkiLHk9IlRvdGFsIExlbmd0aCAoY20pIiwNCiAgICAgICBjb2xvcj0iQ2xhZGUiLHN0YXJzaGFwZT0iQ2xhZGUiLGZpbGw9IkNsYWRlIikrDQogIHNjYWxlX3hfY29udGludW91cyh0cmFucz0nbG9nMTAnKSsNCiAgc2NhbGVfeV9jb250aW51b3VzKHRyYW5zPSdsb2cxMCcpKw0KICBnZW9tX3RleHQoYWVzKHggPSAwLjE4LCB5ID0gMTkwMCwgbGFiZWwgPSBsbV9lcW4uYWxsKGZpdC5zcGVjaWVzX2F2ZXJhZ2UpKSwNCiAgICAgICAgICAgIGhqdXN0PTAsIHBhcnNlID0gVFJVRSxkYXRhLmZyYW1lKCkpKw0KICB0aGVtZV9jbGFzc2ljKCkrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj1jKDAuODUsMC4yKSkNCndyaXRlLmNzdihkYXRhX3NwZWNpZXMlPiVzZWxlY3QodGF4b24sY2xhZGUsTixPT0wsdG90YWxfbGVuZ3RoLGV2ZXJ5dGhpbmcoKSksDQogICAgICAgICAgZmlsZT0ib29sX2RhdGFfY2xlYW5lZF9zcGVjaWVzYXZlcmFnZXMuY3N2IikNCmBgYA0KDQpgYGB7cn0NCnN1bW1hcnkoZml0LnNwZWNpZXNfYXZlcmFnZSkNCnJlZ3Jlc3Npb24uc3RhdHMoZml0LnNwZWNpZXNfYXZlcmFnZSkNCmBgYA0KDQojIyMgU3BlY2llcyBhdmVyYWdlcywgZXhjbHVkaW5nIGFuZ3VpbGxpZm9ybSwgY29tcHJlc3NpZm9ybSwgYW5kIG1hY3J1cmlmb3JtIHRheGENCg0KYGBge3J9DQpzdW1tYXJ5KGZpdC5zcGVjaWVzX2F2ZXJhZ2UyKQ0KcmVncmVzc2lvbi5zdGF0cyhmaXQuc3BlY2llc19hdmVyYWdlMikNCmBgYA0KDQojIyBQZXJjZW50IG9mIHRheGEgdGhhdCBoYXZlIGVzdGltYXRlcyBmYWxsaW5nIHdpdGhpbmcgWCBwZXJjZW50IG9mIHRoZWlyIGFjdHVhbCBsZW5ndGgNCg0KYGBge3J9DQpyYmluZChkYXRhX2ZpbmFsJT4lDQogIG11dGF0ZShmaXQ9ZXhwKHByZWRpY3QoZml0Lk9PTCwuKSkqcmVncmVzc2lvbi5zdGF0cyhmaXQuT09MKSRDRiwNCiAgICAgICAgIFBFPShmaXQtdG90YWxfbGVuZ3RoKS9maXQpJT4lDQogIG11dGF0ZShvbmV0aGlyZD1pZmVsc2UoUEU8MC4zMzMmUEU+KC0wLjMzMyksMSwwKSwNCiAgICAgICAgIG9uZWZpZnRoPWlmZWxzZShQRTwwLjImUEU+KC0wLjIpLDEsMCkpJT4lDQogIGRyb3BfbmEob25ldGhpcmQsb25lZmlmdGgpJT4lDQogIHNlbGVjdChvbmV0aGlyZCxvbmVmaWZ0aCklPiUNCiAgc3VtbWFyaXNlKGFjcm9zcyhvbmV0aGlyZDpvbmVmaWZ0aCx+bWVhbiguKSkpLA0KICAgICAgZGF0YV9zcGVjaWVzJT4lDQogIG11dGF0ZShmaXQ9ZXhwKHByZWRpY3QoZml0LnNwZWNpZXNfYXZlcmFnZSwuKSkqcmVncmVzc2lvbi5zdGF0cyhmaXQuc3BlY2llc19hdmVyYWdlKSRDRiwNCiAgICAgICAgIFBFPShmaXQtdG90YWxfbGVuZ3RoKS9maXQpJT4lDQogIG11dGF0ZShvbmV0aGlyZD1pZmVsc2UoUEU8MC4zMzMmUEU+KC0wLjMzMyksMSwwKSwNCiAgICAgICAgIG9uZWZpZnRoPWlmZWxzZShQRTwwLjImUEU+KC0wLjIpLDEsMCkpJT4lDQogIGRyb3BfbmEob25ldGhpcmQsb25lZmlmdGgpJT4lDQogIHNlbGVjdChvbmV0aGlyZCxvbmVmaWZ0aCklPiUNCiAgc3VtbWFyaXNlKGFjcm9zcyhvbmV0aGlyZDpvbmVmaWZ0aCx+bWVhbiguKSkpDQopJT4lDQogIG11dGF0ZShhY3Jvc3MoZXZlcnl0aGluZygpLH4uKjEwMCksDQogICAgICAgICByb3duYW1lPWMoIkluZGl2aWR1YWwgU3BlY2ltZW5zIiwiU3BlY2llcyBBdmVyYWdlcyIpKSU+JQ0KICBjb2x1bW5fdG9fcm93bmFtZXMoInJvd25hbWUiKSU+JQ0KICBrYWJsZShkaWdpdHM9MixhbGlnbj1jKCJsIiwiYyIsImMiKSwNCiAgICAgICAgY29sLm5hbWVzID0gYygiKy8tIDMzJSIsIisvLSAyMCUiKSklPiUNCiAgYWRkX2hlYWRlcl9hYm92ZShjKCIgIj0xLCJQZXJjZW50IG9mIG9ic2VydmF0aW9ucyB3aXRoaW4gWCBwZXJjZW50IG9mIGFjdHVhbCB2YWx1ZSI9MikpJT4lDQogIGthYmxlX3N0eWxpbmcoKQ0KYGBgDQoNCkJhc2VkIG9uIHRoaXMsIGVzdGltYXRlZCBsZW5ndGhzIGZvciB0aGUgbWFqb3JpdHkgb2YgdGF4YSBmYWxsIHdpdGhpbiArLy0gMjAlIG9mIHRoZSBhY3R1YWwgdmFsdWUsIGFuZCBuZWFybHkgYWxsIHRheGEgZmFsbCB3aXRoaW4gKy8tIDMzJSBvZiB0aGUgYWN0dWFsIHZhbHVlLiBUaGUgdGF4YSB0aGF0IGRvIG5vdCB0ZW5kIHRvIGJlIHRob3NlIHdpdGggZXh0cmVtZSBib2R5IHBsYW5zIChlLmcuLCBoeXBlci1hbmd1aWxsaWZvcm0sIGh5cGVyLWNvbXByZXNzaWZvcm0sIGV4dHJlbWVseSBwb3N0ZXJpb3JseSBzaGlmdGVkIG9yYml0KSwgd2hpY2ggc3VnZ2VzdHMgdGhhdCB0aGUgcmV0dXJuZWQgOTUlIHByZWRpY3Rpb24gaW50ZXJ2YWxzIGZvciB0aGUgbW9kZWwgKHdoaWNoIHRlbmQgdG8gYmUgKy8tIDUwJSBvciBtb3JlIGVzdGltYXRlZCBsZW5ndGgpIGFyZSBub3QgYWNjdXJhdGUgYW5kIHVzaW5nICsvLSAlUEUgaXMgYSBtb3JlIGFjY3VyYXRlIHdheSBvZiBleHByZXNzaW5nIHVuY2VydGFpbnR5IGluIGxlbmd0aCBlc3RpbWF0ZXMgZm9yIGFydGhyb2RpcmVzLg0KDQojIyBEaXN0cmlidXRpb24gb2YgaGVhZCBwcm9wb3J0aW9ucyBhY3Jvc3MgY2xhZGVzDQoNCihyZWY6aGVhZHByb3BvcnRpb25zKSBEaXN0cmlidXRpb25zIG9mIGhlYWQgbGVuZ3RoIGFzIGEgcGVyY2VudCBvZiB0b3RhbCBsZW5ndGggKHRvcCksIE9PTCBhcyBhIHBlcmNlbnQgb2YgdG90YWwgbGVuZ3RoIChtaWRkbGUpLCBhbmQgT09MIGFzIGEgcGVyY2VudCBvZiB0b3RhbCBsZW5ndGggbWludXMgc25vdXQgbGVuZ3RoIChib3R0b20pIGluIGZpc2hlcyBleGFtaW5lZCBpbiB0aGlzIHN0dWR5LiBEYXRhIHBvaW50cyBmb3IgdGhlc2UgYm94LWFuZC13aGlza2VyIHBsb3RzIGFyZSBzcGVjaWVzLWF2ZXJhZ2VzLg0KDQpgYGB7cixmaWcuY2FwPSIocmVmOmhlYWRwcm9wb3J0aW9ucykiLGZpZy5hc3A9MS43NSxjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30NCmdyaWQuYXJyYW5nZSgNCmdncGxvdChkYXRhX2ZpbmFsJT4lZmlsdGVyKGxlbmd0aF9hcyE9ImVzdGltYXRlZCB0LmwuIiwhaXMubmEodG90YWxfbGVuZ3RoKSwhaXMubmEoaGVhZF9sZW5ndGgpKSU+JQ0KICAgICAgICAgZ3JvdXBfYnkodGF4b24pJT4lDQogICAgICAgICBzdW1tYXJpc2UodG90YWxfbGVuZ3RoPW1lYW4odG90YWxfbGVuZ3RoKSxoZWFkX2xlbmd0aD1tZWFuKGhlYWRfbGVuZ3RoKSxjbGFkZT11bmlxdWUoY2xhZGUpKSwNCiAgICAgICBhZXMoeT1oZWFkX2xlbmd0aC90b3RhbF9sZW5ndGgseD1jbGFkZSkpKw0KICBnZW9tX2hsaW5lKGFlcyh5aW50ZXJjZXB0PW1lYW4oaGVhZF9sZW5ndGgvdG90YWxfbGVuZ3RoKSkpKw0KICBnZW9tX2hsaW5lKGRhdGE9LiU+JWZpbHRlcihjbGFkZT09IlBsYWNvZGVybWkiKSxjb2xvcj1odWVfcGFsKCkoNSlbNF0sDQogICAgICAgICAgICAgYWVzKHlpbnRlcmNlcHQ9bWVhbihoZWFkX2xlbmd0aC90b3RhbF9sZW5ndGgpKSkrDQogIGdlb21fdmlvbGluKGFlcyhmaWxsPWNsYWRlKSxzY2FsZT0id2lkdGgiKSsNCiAgZ2VvbV9ib3hwbG90KHdpZHRoPTAuMykrDQogIGxhYnMoeD0iQ2xhZGUiLHk9IlBlcmNlbnQgb2YgVG90YWwgTGVuZ3RoIikrDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHM9c2NhbGVzOjpsYWJlbF9wZXJjZW50KGFjY3VyYWN5ID0gMSkpKw0KICBnZ3RpdGxlKCJIZWFkIExlbmd0aCBhcyBQZXJjZW50IG9mIFRvdGFsIExlbmd0aCIpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSwNCmdncGxvdChkYXRhX2ZpbmFsJT4lZmlsdGVyKGxlbmd0aF9hcyE9ImVzdGltYXRlZCB0LmwuIiwhaXMubmEodG90YWxfbGVuZ3RoKSwhaXMubmEoT09MKSklPiUNCiAgICAgICAgIGdyb3VwX2J5KHRheG9uKSU+JQ0KICAgICAgICAgc3VtbWFyaXNlKHNub3V0X2xlbmd0aD1tZWFuKHNub3V0X2xlbmd0aCksdG90YWxfbGVuZ3RoPW1lYW4odG90YWxfbGVuZ3RoKSxPT0w9bWVhbihPT0wpLGNsYWRlPXVuaXF1ZShjbGFkZSkpLA0KICAgICAgIGFlcyh5PU9PTC90b3RhbF9sZW5ndGgseD1jbGFkZSkpKw0KICBnZW9tX2hsaW5lKGFlcyh5aW50ZXJjZXB0PW1lYW4oT09ML3RvdGFsX2xlbmd0aCkpKSsNCiAgZ2VvbV9obGluZShkYXRhPS4lPiVmaWx0ZXIoY2xhZGU9PSJQbGFjb2Rlcm1pIiksY29sb3I9aHVlX3BhbCgpKDUpWzRdLA0KICAgICAgICAgICAgIGFlcyh5aW50ZXJjZXB0PW1lYW4oT09ML3RvdGFsX2xlbmd0aCkpKSsNCiAgZ2VvbV92aW9saW4oYWVzKGZpbGw9Y2xhZGUpLHNjYWxlPSJ3aWR0aCIpKw0KICBnZW9tX2JveHBsb3Qod2lkdGg9MC4zKSsNCiAgbGFicyh4PSJDbGFkZSIseT0iUGVyY2VudCBvZiBUb3RhbCBMZW5ndGgiKSsNCiAgZ2d0aXRsZSgiT09MIGFzIFBlcmNlbnQgb2YgVG90YWwgTGVuZ3RoIikrDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHM9c2NhbGVzOjpsYWJlbF9wZXJjZW50KGFjY3VyYWN5ID0gMSkpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSwNCmdncGxvdChkYXRhX2ZpbmFsJT4lZmlsdGVyKGxlbmd0aF9hcyE9ImVzdGltYXRlZCB0LmwuIiwhaXMubmEodG90YWxfbGVuZ3RoKSwhaXMubmEoT09MKSwhaXMubmEoc25vdXRfbGVuZ3RoKSklPiUNCiAgICAgICAgIGdyb3VwX2J5KHRheG9uKSU+JQ0KICAgICAgICAgc3VtbWFyaXNlKHNub3V0X2xlbmd0aD1tZWFuKHNub3V0X2xlbmd0aCksdG90YWxfbGVuZ3RoPW1lYW4odG90YWxfbGVuZ3RoKSxPT0w9bWVhbihPT0wpLGNsYWRlPXVuaXF1ZShjbGFkZSkpLA0KICAgICAgIGFlcyh5PU9PTC8odG90YWxfbGVuZ3RoLXNub3V0X2xlbmd0aCkseD1jbGFkZSkpKw0KICBnZW9tX2hsaW5lKGFlcyh5aW50ZXJjZXB0PW1lYW4oT09MLyh0b3RhbF9sZW5ndGgtc25vdXRfbGVuZ3RoKSkpKSsNCiAgZ2VvbV9obGluZShkYXRhPS4lPiVmaWx0ZXIoY2xhZGU9PSJQbGFjb2Rlcm1pIiksY29sb3I9aHVlX3BhbCgpKDUpWzRdLA0KICAgICAgICAgICAgIGFlcyh5aW50ZXJjZXB0PW1lYW4oT09MLyh0b3RhbF9sZW5ndGgtc25vdXRfbGVuZ3RoKSkpKSsNCiAgZ2VvbV92aW9saW4oYWVzKGZpbGw9Y2xhZGUpLHNjYWxlPSJ3aWR0aCIpKw0KICBnZW9tX2JveHBsb3Qod2lkdGg9MC4zKSsNCiAgbGFicyh4PSJDbGFkZSIseT0iUGVyY2VudCBvZiBUb3RhbCBMZW5ndGgiKSsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscz1zY2FsZXM6OmxhYmVsX3BlcmNlbnQoYWNjdXJhY3kgPSAxKSkrDQogIGdndGl0bGUoIk9PTCBhcyBQZXJjZW50IG9mIFRvdGFsIExlbmd0aCBtaW51cyBTbm91dCBMZW5ndGgiKSsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikpDQpgYGANCg0KT3ZlcmFsbCwgcGxhY29kZXJtcyBzZWVtIHRvIHNob3cgaGVhZHMgdGhhdCBhcmUgY29tcGFyYWJsZSBpbiByZWxhdGl2ZSBsZW5ndGggdG8gb3RoZXIgZmlzaGVzLiBPT0wgYXMgcGVyY2VudCB0b3RhbCBsZW5ndGggaXMgc2xpZ2h0bHkgaGlnaCAodGhvdWdoIHN0aWxsIHdpdGhpbiB0aGUgcmFuZ2Ugb2YgdmFyaWF0aW9uKSwgYnV0IHRoaXMgYXBwZWFycyB0byBiZSBkdWUgdG8gdGhlIHVudXN1YWxseSBzaG9ydCBzbm91dCBvZiBhcnRocm9kaXJlcyByZWxhdGl2ZSB0byBvdGhlciBnbmF0aG9zdG9tZXMuDQoNCiMjIFRlc3RpbmcgdG8gc2VlIGlmIGRpZmZlcmVudCBjbGFkZXMgaGF2ZSBkaWZmZXJlbnQgcmVncmVzc2lvbiBsaW5lcw0KDQpgYGB7cn0NCnN1bW1hcnkobG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCkrY2xhZGUsDQogICAgICAgICAgIGRhdGFfZmluYWwpKQ0KYGBgDQoNCmBgYHtyfQ0Kb3B0aW9ucyh3aWR0aD0xMDAwKQ0Kc3VtbWFyeShsbShsb2codG90YWxfbGVuZ3RoKX5sb2coT09MKSpjbGFkZSwNCiAgICAgICAgICAgZGF0YV9maW5hbCkpDQpgYGANCg0KQ29tcGFyaW5nIHJlZ3Jlc3Npb24gbGluZXMgd2l0aCBib3RoIHNoYXBlIGFuZCBjbGFkZQ0KDQpgYGB7cn0NCm9wdGlvbnMod2lkdGg9MTAwMCkNCnN1bW1hcnkobG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCkqY2xhZGUrc2hhcGUsDQogICAgICAgICAgIGRhdGFfZmluYWwpKQ0KYW5vdmEobG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCkqY2xhZGUrc2hhcGUsDQogICAgICAgICAgIGRhdGFfZmluYWwpKQ0KYGBgDQoNCkNvbXBhcmluZyByZWdyZXNzaW9uIGxpbmVzIHdpdGggYm90aCBzaGFwZSBhbmQgY2xhZGUsIHVzaW5nIHNwZWNpZXMgYXZlcmFnZXMNCg0KYGBge3J9DQpvcHRpb25zKHdpZHRoPTEwMDApDQpkYXRhX3NwZWNpZXMgJT4lDQogIGxtKGxvZyh0b3RhbF9sZW5ndGgpfmxvZyhPT0wpKmNsYWRlK3NoYXBlLC4pJT4lDQogIHN1bW1hcnkoKQ0KYGBgDQoNCklmIGV4YW1pbmluZyBiYXNlZCBvbiBzcGVjaWVzIGF2ZXJhZ2VzIHNoYXJrcyBhbmQgb3RoZXIgZmlzaCBncm91cHMgZG8gbm90IGhhdmUgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZXMgaW4gc2xvcGUgYmV0d2VlbiBjbGFkZXMuDQoNCihyZWY6c2hhcmthbGxvbWV0cnkpIFBsb3Qgb2YgT09MIGFnYWluc3QgdG90YWwgbGVuZ3RoIGluIGZpc2hlcyBvbiBhIGxvZzxzdWI+MTA8L3N1Yj4gc2NhbGUsIGZvY3VzaW5nIG9uIHNoYXJrcywgc2hvd2luZyB0aGUgbm9uLXJhbmRvbSBkaXN0cmlidXRpb24gb2Ygc2hhcmtzIHdpdGggcmVzcGVjdCB0byBzaXplIGFuZCBib2R5IHNoYXBlLiBUaGUgdmVyeSBzbWFsbGVzdCBzaGFya3MgKGUuZy4sIFNjeWxpb3JoaW5pZGFlKSBhbGwgaGF2ZSBlbG9uZ2F0ZSBib2RpZXMsIHdoZXJlYXMgdGhlIHZlcnkgbGFyZ2VzdCBzaGFya3MgdGVuZCB0byBiZSBMYW1uaWZvcm1lcyAoTGFtbmlkYWUsIE1lZ2FjaGFzbWF0aWRhZSkgb3IgRWNoaW5vcmhpbmlmb3JtZXMgd2l0aCB2ZXJ5IHNob3J0IHRydW5rIHByb3BvcnRpb25zIChhYmJyZXZpYXRlZCBhcyAiTGFtLi9NZWdhYy4vRWNoaW4uIiBpbiB0aGUgbGVnZW5kKS4gVGh1cywgYWx0aG91Z2ggY2hvbmRyaWNodGh5YW5zIHNob3cgYSBkaWZmZXJlbnQgcmVncmVzc29uIGxpbmVmcm9tIG90aGVyIHRheGEsIHRoaXMgaXMgbm90IGR1ZSB0byBhIGRpZmZlcmVudCBhbGxvbWV0cmljIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIE9PTCBhbmQgdG90YWwgbGVuZ3RoIGJ1dCByYXRoZXIgZHVlIHRvIG5vbi1yYW5kb20gZGlmZmVyZW5jZXMgaW4gZWNvbG9neSB0aGF0IGFyZSBtaXJyb3JlZCBpbiBvdGhlciBmaXNoZXMgKGkuZS4sIHR1bmFzIGFsc28gc2hvdyBzaG9ydGVyIGJvZGllcyB0aGFuIG90aGVyIGZpc2hlcykuIEJsYWNrIGxpbmUgcmVwcmVzZW50cyByZWdyZXNzaW9uIGZvciBjaG9uZHJpY2h0aHlhbnMgd2hlcmVhcyBibHVlIGxpbmUgcmVwcmVzZW50cyByZWdyZXNzaW9uIGZvciBhbGwgZmlzaGVzLg0KDQpgYGB7cixmaWcuY2FwPSIocmVmOnNoYXJrYWxsb21ldHJ5KSIsY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZSd9DQpkYXRhX2ZpbmFsJT4lDQogIGF1Z21lbnQoZml0Lk9PTCxuZXdkYXRhPS4saW50ZXJ2YWw9InByZWRpY3QiKSU+JQ0KICBtdXRhdGUoYWNyb3NzKC5maXR0ZWQ6LnVwcGVyLH5leHAoLikqcmVncmVzc2lvbi5zdGF0cyhmaXQuT09MKSRDRikpJT4lDQogIGRyb3BfbmEodG90YWxfbGVuZ3RoLE9PTCklPiUNCiAgZ2dwbG90KC4sDQogICAgICAgYWVzKHk9dG90YWxfbGVuZ3RoLHg9T09MKSkrDQogIGdlb21fcG9pbnQoYWVzKHNoYXBlPWNsYWRlKSxjb2xvcj0iZGFyayBncmV5IixmaWxsPSJsaWdodCBncmV5IixzaG93LmxlZ2VuZD1GKSsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSJsbSIsZm9ybXVsYT15fngsYWxwaGE9MC4yNSxzZT1GKSsNCiAgZ2VvbV9wb2ludChkYXRhPS4lPiVmaWx0ZXIoY2xhZGU9PSJDaG9uZHJpY2h0aHllcyIpJT4lDQogICAgICAgICAgICAgICBkcm9wX25hKHNoYXBlKSU+JQ0KICAgICAgICAgICAgICAgbXV0YXRlKHNoYXBlPWZhY3RvcihpZmVsc2UoZmFtaWx5ICVpbiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYygiTGFtbmlkYWUiLCJNZWdhY2hhc21hdGlkYWUiLCJFY2hpbm9yaGluaWRhZSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkxhbS4vTWVnYWMuL0VjaGluLiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5jaGFyYWN0ZXIoc2hhcGUpKSksDQogICAgICAgICAgICAgICAgICAgICAgc2hhcGU9cmVsZXZlbChmYWN0b3Ioc2hhcGUpLCJmdXNpZm9ybSIpKSwNCiAgICAgICAgICAgICBhZXMoc2hhcGU9Y2xhZGUsZmlsbD1zaGFwZSkpKw0KICBnZW9tX3Ntb290aChkYXRhPS4lPiVmaWx0ZXIoY2xhZGU9PSJDaG9uZHJpY2h0aHllcyIpLGNvbG9yPSJibGFjayIsDQogICAgICAgICAgICAgIG1ldGhvZD0ibG0iLGZvcm11bGE9eX54LGFscGhhPTAuMjUpKw0KICBnZW9tX2xpbmUoYWVzKHk9Lmxvd2VyKSwgY29sb3IgPSAiIzMzNjZGRiIsIGxpbmV0eXBlID0gImRhc2hlZCIsYWxwaGE9MC41KSsNCiAgZ2VvbV9saW5lKGFlcyh5PS51cHBlciksIGNvbG9yID0gIiMzMzY2RkYiLCBsaW5ldHlwZSA9ICJkYXNoZWQiLGFscGhhPTAuNSkrDQogIHNjYWxlX3NoYXBlX21hbnVhbCh2YWx1ZXM9YygyMSwyNCwyMiwyMywyNSksZ3VpZGU9Im5vbmUiKSsNCiAgc2NhbGVfeF9jb250aW51b3VzKHRyYW5zPSdsb2cxMCcpKw0KICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnM9J2xvZzEwJykrDQogIGxhYnMoeD0iT3JiaXQtT3BlcmN1bGFyIExlbmd0aCAoY20pIix5PSJUb3RhbCBMZW5ndGggKGNtKSIsZmlsbD0iQm9keSBTaGFwZSIpKw0KICB0aGVtZV9jbGFzc2ljKCkrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj1jKDAuNywwLjI1KSkrDQogIGd1aWRlcyhmaWxsPWd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNoYXBlID0gMjQpKSkNCmBgYA0KDQojIyBFZmZlY3RzIG9mIG9udG9nZW55IG9uIG1vZGVsIGVycm9yDQoNCiMjIyBFZmZlY3RzIG9mIG9udG9nZW55IHdpdGggZm9yIHNtYWxsZXIgYWN0aW5vcHRlcnlnaWFucyB7I3JheWZpbm9udG9nZW55fQ0KDQoqKk5vdGU6KiogdGhlc2UgZGF0YSB1c2UgdGhlIGluaXRpYWwgZGF0YSBzZXQgdGhhdCAqZG9lcyBub3QqIGZpbHRlciBvdXQgdGhlIGNvbmZpcm1lZCBqdXZlbmlsZSBpbmRpdmlkdWFscyBwcmlvciB0byB0aGUgYW5hbHlzaXMuDQoNCmBgYHtyLGZpZy5jYXA9IlBsb3Qgb2YgcmVzaWR1YWxzIHZlcnN1cyB0b3RhbCBsZW5ndGggZm9yIHNtYWxsZXIgZmlzaGVzIHRoYXQgY291bGQgYmUgbWVhc3VyZWQgZGlyZWN0bHkifQ0KZGF0YV9pbml0aWFsJT4lDQogIGRyb3BfbmEoT09MLHRvdGFsX2xlbmd0aCklPiUNCiAgZmlsdGVyKHJlZmVyZW5jZXMhPSJTbGFnbGUgcGVycy4gY29tbS4iKSU+JQ0KI1JlbW92ZWQgdGhpcyBvbmUgYmVjYXVzZSBJIGNvdWxkIG9ubHkgbWVhc3VyZSBpdCBmcm9tIGEgcGhvdG8gYXQgYW4gYW5nbGUsIG5vdCBwZXJzb25hbGx5LCBhbmQgaXQgaXMgbXVjaCBiaWdnZXIgdGhhbiBhbGwgb3RoZXIgc3BlY2ltZW5zLCBzbyBJIGFtIG5vdCAxMDAlIHN1cmUgaWYgaXQgaXMgcmVsaWFibGUNCiAgYXVnbWVudChmaXQuT09MLG5ld2RhdGE9LiklPiUNCiAgZmlsdGVyKHRheG9uICVpbiUgYygiSGlvZG9uX3Rlcmdpc3VzIiwNCiAgICAgICAgICAgICAgICAgICAgICAiQW1ibG9waXRlc19ydXBlc3RyaXMiLA0KICAgICAgICAgICAgICAgICAgICAgICJBbG9zYV9wc2V1ZG9oYXJlbmd1cyIsDQogICAgICAgICAgICAgICAgICAgICAgIk5lb2dvYml1c19tZWxhbm9zdG9tdXMiLA0KICAgICAgICAgICAgICAgICAgICAgICJFdGhlb3N0b21hX2NhZXJ1bGV1bSIsDQogICAgICAgICAgICAgICAgICAgICAgIk1pY3JvcHRlcnVzX2RvbG9taWV1IikpJT4lDQogIGdncGxvdChhZXModG90YWxfbGVuZ3RoLC5yZXNpZCkpKw0KICBnZW9tX3BvaW50KGFlcyhmaWxsPWdlbnVzKSxzaGFwZT0yMSkrDQogIGdlb21fc21vb3RoKGFlcyhjb2xvcj1nZW51cyksZm9ybXVsYT15fngsbWV0aG9kPSJsbSIpKw0KICBsYWJzKHg9IlRvdGFsIExlbmd0aCAoY20pIix5PSJSZXNpZHVhbHMiLGZpbGw9IlNwZWNpZXMiLHNoYXBlPSJTcGVjaWVzIixjb2xvcj0iU3BlY2llcyIpKw0KICB0aGVtZShsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChmYWNlID0gIml0YWxpYyIpKQ0KYGBgDQoNCiMjIyBFZmZlY3RzIG9mIG9udG9nZW55IHdpdGggZm9yICpHZW90cmlhKiB7I2xhbXByZXlvbnRvZ2VueX0NCg0KKHJlZjpsYW1wcmV5b250b2dlbnkpIFBsb3Qgb2YgcmVzaWR1YWxzIHZlcnN1cyB0b3RhbCBsZW5ndGggaW4gdGhlIGxhbXByZXkgKkdlb3RyaWEgbWFjcm9zdG9tYSogdXNpbmcgZGF0YSBmcm9tIEJha2VyIGV0IGFsLiAoMjAyMSkuDQoNCmBgYHtyLGZpZy5jYXA9IihyZWY6bGFtcHJleW9udG9nZW55KSJ9DQpkYXRhX2luaXRpYWwlPiUNCiAgZmlsdGVyKHRheG9uPT0iR2VvdHJpYV9tYWNyb3N0b21hIikgJT4lDQogIGF1Z21lbnQoZml0Lk9PTCxuZXdkYXRhPS4pJT4lDQogIGdncGxvdCguLGFlcyh0b3RhbF9sZW5ndGgsLnJlc2lkKSkrDQogIGdlb21fcG9pbnQoZmlsbD0iZ3JheSIsc2hhcGU9MjEpKw0KICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIixmb3JtdWxhPXl+eCkrDQogIGxhYnMoeD0iVG90YWwgTGVuZ3RoIChjbSkiLHk9IlJlc2lkdWFscyIpDQpgYGANCg0KVGhlcmUgaXMgYSBzbGlnaHRseSBwb3NpdGl2ZSBhbGxvbWV0cnkgaW4gdGhlIHJlc2lkdWFscyBmb3IgbGFtcHJleXMsIHRob3VnaCBub3QgYXMgc3Ryb25nIGFzIGZvciBhY3Rpbm9wdGVyeWdpYW5zLg0KDQojIyMgRWZmZWN0cyBvZiBvbnRvZ2VueSB3aXRoIHNoYXJrcyBhbmQgbGFyZ2UsIHBlbGFnaWMgZmlzaGVzIHsjc2hhcmtvbnRvZ2VueX0NCg0KKHJlZjpzaGFya29udG9nZW55Y2FwKSBQbG90IG9mIHJlc2lkdWFscyB2ZXJzdXMgdG90YWwgbGVuZ3RoIGZvciBsYXJnZXIgZmlzaCBzcGVjaWVzIHdpdGggbGFyZ2Ugc2FtcGxlIHNpemVzLiBDaG9uZHJpY2h0aHlhbnMgYXJlIHJlcHJlc2VudGVkIGJ5IHRyaWFuZ2xlcyBhbmQgZGFzaGVkIGxpbmVzIHdoZXJlYXMgYWN0aW5vcHRlcnlnaWFucyBhcmUgcmVwcmVzZW50ZWQgYnkgY2lyY2xlcyBhbmQgc29saWQgbGluZXMuDQoNCmBgYHtyLGZpZy5jYXA9IihyZWY6c2hhcmtvbnRvZ2VueWNhcCkifQ0KZGF0YV9pbml0aWFsICU+JSANCiAgZmlsdGVyKHRheG9uICVpbiUgYygiTWFrYWlyYV9uaWdyaWNhbnMiLCAiS2FqaWtpYV9hdWRheCIsICJLYWppa2lhX2FsYmlkYSIsDQogICAgICAgICAgICAgICAgICAgICAgIkFjYW50aG9jeWJpdW1fc29sYW5kcmkiLCAiVGh1bm51c190aHlubnVzIiwgIkNhcmNoYXJoaW51c19wbHVtYmV1cyIsDQogICAgICAgICAgICAgICAgICAgICAgIkNhcmNoYXJoaW51c19sZXVjYXMiLCAiUmhpem9wcmlvbm9kb25fYWN1dHVzIiwgIkVjaGlub3JoaW51c19jb29rZWkiKSkgJT4lDQogIG11dGF0ZSh0YXhvbj1nc3ViKCJfIiwiICIsdGF4b24pKSU+JQ0KICBhdWdtZW50KGZpdC5PT0wsbmV3ZGF0YT0uKSU+JQ0KICBkcm9wX25hKC5yZXNpZCx0b3RhbF9sZW5ndGgpJT4lDQogIGdncGxvdChhZXModG90YWxfbGVuZ3RoLC5yZXNpZCkpKw0KICBnZW9tX3BvaW50KGFlcyhmaWxsPXRheG9uLHNoYXBlPWNsYWRlKSkrDQogIHNjYWxlX3NoYXBlX21hbnVhbCh2YWx1ZXM9YygyMSwyNCksZ3VpZGU9Im5vbmUiKSsNCiAgc2NhbGVfbGluZXR5cGUoZ3VpZGU9Im5vbmUiKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKGNvbG9yPXRheG9uLGxpbmV0eXBlPWNsYWRlKSxtZXRob2Q9ImxtIixmb3JtdWxhPXl+eCxzZT1GKSsNCiAgZ2VvbV9wb2ludChhZXMoZmlsbD10YXhvbixzaGFwZT1jbGFkZSksDQogICAgICAgICAgICAgLiU+JWZpbHRlcih0YXhvbj09IkVjaGlub3JoaW51cyBjb29rZWkiLHRvdGFsX2xlbmd0aDwxMDApKSsNCiAgbGFicyh4PSJUb3RhbCBMZW5ndGggKGNtKSIseT0iUmVzaWR1YWxzIixjb2xvcj0iU3BlY2llcyIsZmlsbD0iU3BlY2llcyIsc2hhcGU9IkNsYWRlIiwNCiAgICAgICBsaW5ldHlwZT0iQ2xhZGUiKSsNCiAgdGhlbWUobGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJpdGFsaWMiKSkrDQogIGd1aWRlcyhmaWxsPWd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNoYXBlID0gYygyMSwyNCwyNCwyNCwyMSwyMSwyMSwyNCwyMSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbmV0eXBlID0gYygic29saWQiLCJkb3R0ZWQiLCJkb3R0ZWQiLCJkb3R0ZWQiLCJzb2xpZCIsInNvbGlkIiwic29saWQiLCJkb3R0ZWQiLCJzb2xpZCIpKSkpDQpgYGANCg0KQXMgY2FuIGJlIHNlZW4sIHRoZXJlIGlzIGEgY29uc2lzdGVudCBuZWdhdGl2ZSBhbGxvbWV0cnkgYmV0d2VlbiBPT0wgYW5kIHRvdGFsIGxlbmd0aCBpbiByYXktZmlubmVkIGZpc2hlcywgYW5kIHRvIGEgbGVzc2VyIGRlZ3JlZSBsYW1wcmV5cy4gQnkgY29udHJhc3QsIG5vIGNsZWFyIHJlbGF0aW9uc2hpcCBpcyBwcmVzZW50IGluIHNoYXJrcy4NCg0KIyMjIEVmZmVjdHMgb2Ygb250b2dlbnkgd2l0aCBmb3IgKkV1c3RoZW5vcHRlcm9uKiBmcm9tIFNjaHVsdHplICgxOTg0KSB7I0V1c3RoZW5vcHRlcm9ufQ0KDQpgYGB7cn0NCmV1c3RoZW5vcHRlcm9uPC1kYXRhLmZyYW1lKA0KICBzbm91dF9sZW5ndGg9YygxLjUsMS4yLE5BLE5BLDIuMCwyLjgsMi44LDIuNywyLjcsMi42LE5BLDIuNCwyLjcsMi45LDIuOSxOQSxOQSxOQSxOQSxOQSw0LjQsMy4zLA0KICAzLjMsMy42LE5BLE5BLE5BLE5BLE5BLDMuNCxOQSxOQSxOQSxOQSxOQSwxMS4xLE5BLE5BLE5BLE5BLE5BLE5BLE5BLDEyLDE0LjUsMTMuOCwNCiAgTkEsTkEsMTkuOCwzMiwwKSwNCiAgaGVhZF9sZW5ndGg9Yyg3LjUsNy4yLDguMiw4LjIsMTEuMSwxMS43LDExLjcsMTEuNywxMS43LDEzLjMsTkEsMTIuNiwxNCwxNC4yLDE0LjYsDQogICAgICAgICAgICAgICAgMTYuMiwxOS42LDE2LjcsMjAuOSwyMC4xLDE5LjUsTkEsTkEsMjIuMSwyNS4zLDI2LjEsTkEsMjYuMywyNS40LDI3LjIsDQogICAgICAgICAgICAgICAgMzAuOCwzMi44LDMxLjksMzkuOSwzOS4yLDU2LjMsNTEuNCw1MS4yLDQ1LjIsNDUuMixOQSw1My4zLDU0LjksNjQuNiwNCiAgICAgICAgICAgICAgICA3Miw3Ni41LDYzLjksODEuMSw4Ny41LDEzNS41LDE5MSksDQogIHByZWNhdWRhbF9sZW5ndGg9YygyNy4yLDI3LjIsMjguOSwyOC4zLE5BLDQ0LjMsNDQuMyw1MC44LDUwLjgsNDUsNDUsNTMuNyxOQSxOQSwNCiAgICAgICAgICAgICAgICAgICAgIDU4LjMsNjIuNyw2NCw2Ny43LDc4LjMsNzguNCw3OC40LDkzLjEsOTMuMSxOQSwxMDAuNCwxMTUuNixOQSxOQSwNCiAgICAgICAgICAgICAgICAgICAgIDEyMC45LE5BLE5BLE5BLDEzMy43LDE2OC4zLDE2OC4zLDIwMC40LDIwMC44LDIxMC4zLDIxOC45LDIxOC45LA0KICAgICAgICAgICAgICAgICAgICAgTkEsMjQ4LjIsMjUyLjYsMjYyLjEsMzE3LjMsMzQyLjMsMzcxLjksMzc2LjQsTkEsTkEsNzkzLjkpLA0KICB0b3RhbF9sZW5ndGg9YygyOCwyOCwzMS44LDMyLjIsTkEsNDgsNDgsNTEuNSw1MS41LDQ5LjIsTkEsNTgsTkEsTkEsTkEsNzAuNCw2Ny4yLA0KICAgICAgICAgICAgICAgTkEsODYuNCw4NC4zLDgzLjEsOTYuNSw5Ni41LE5BLDExMy43LDEyOS42LDEyMC45LE5BLE5BLE5BLE5BLE5BLA0KICAgICAgICAgICAgICAgTkEsTkEsTkEsMjE1LjksTkEsTkEsMjI3LDIyNyxOQSxOQSxOQSxOQSwzMzEuOSwzNDUuOSxOQSxOQSxOQSxOQSw4MDguNSkNCiklPiUNCiAgbXV0YXRlKE9PTD1oZWFkX2xlbmd0aC1zbm91dF9sZW5ndGgpICU+JQ0KICBhdWdtZW50KGxtKHRvdGFsX2xlbmd0aH5wcmVjYXVkYWxfbGVuZ3RoLC4pLG5ld2RhdGE9LiklPiUNCiAgbXV0YXRlKGVzdF9sZW5ndGg9aWZlbHNlKCFpcy5uYSh0b3RhbF9sZW5ndGgpLHRvdGFsX2xlbmd0aCwuZml0dGVkKSkNCg0KZXVzdGhlbm9wdGVyb24lPiUNCiAgbG0obG9nKGVzdF9sZW5ndGgpfmxvZyhPT0wpLC4pJT4lc3VtbWFyeSgpDQpgYGANCiAgDQpgT09MYCBpcyBhbHNvIHJlY292ZXJlZCBhcyBpc29tZXRyaWMgd2l0aCBgdG90YWxfbGVuZ3RoYCBmb3IgKkV1c3RoZW5vcHRlcm9uKiBiYXNlZCBvbiB0aGUgZGF0YSBmb3IgU2NodWx0emUgKDE5ODQpLCB0aG91Z2ggdGhhdCBzdHVkeSBpbmNsdWRlcyB2ZXJ5IGZldyBpbmRpdmlkdWFscyBiZXR3ZWVuIDQwLTgwIGNtLg0KDQojIEVmZmVjdCBvZiBwaHlsb2dlbmV0aWMgcG9zaXRpb24gb24gcmVncmVzc2lvbiBtb2RlbA0KDQojIyBFeGFtaW5pbmcgaWYgcmVzaWR1YWxzIGFyZSBjb25zaXN0ZW50IGFjcm9zcyBtYWpvciBjbGFkZXMgb2YgZmlzaGVzDQoNCmBgYHtyfQ0KT09MX3Jlc2lkdWFsczwtZGF0YV9maW5hbCU+JQ0KICBmaWx0ZXIoIWlzLm5hKE9PTCkpJT4lDQogIGZpbHRlcighaXMubmEodG90YWxfbGVuZ3RoKSklPiUNCiAgbXV0YXRlKHJlc2lkdWFscz0ocmVzaWR1YWxzKGxtKGxvZyh0b3RhbF9sZW5ndGgpfmxvZyhPT0wpKSkpKQ0KYGBgDQoNCioqTXV0YXRpbmcgZmFjdG9yIGZvciBtb3JlIGluY2x1c2l2ZSBmaXNoIGNsYWRlcyoqDQoNCmBgYHtyLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KcmVzaWR1YWxzMjwtT09MX3Jlc2lkdWFscyU+JQ0KICBmaWx0ZXIoIWlzLm5hKHJlc2lkdWFscyksbGVuZ3RoX2FzIT0iZXN0aW1hdGVkIHQubC4iLA0KICAgICAgICAgIShleHRpbmN0PT1UJmNsYWRlPT0iQ2hvbmRyaWNodGh5ZXMiKSklPiUNCiAgZ3JvdXBfYnkodGF4b24pJT4lDQogIHN1bW1hcml6ZShyZXNpZHVhbHM9bWVhbihyZXNpZHVhbHMpLG9yZGVyPXVuaXF1ZShvcmRlciksDQogICAgICAgICAgICBmYW1pbHk9dW5pcXVlKGZhbWlseSksc2hhcGU9dW5pcXVlKHNoYXBlKSxoaWdoZXJfZ3JvdXA9dW5pcXVlKGhpZ2hlcl9ncm91cCkpJT4lDQogIG11dGF0ZShoaWdoZXJfZ3JvdXA9ZmFjdG9yKGhpZ2hlcl9ncm91cCxsZXZlbHM9YygiUGV0cm9teXpvbnRpZm9ybWVzIiwiQXJ0aHJvZGlyYSIsIkNob25kcmljaHRoeWVzIiwiU2FyY29wdGVyeWdpaSIsIkJhc2FsIEFjdGlub3B0ZXJ5Z2lpIiwiQmFzYWwgVGVsZW9zdGVpIiwiT3N0YXJpb3BoeXNpIiwiU3RlbSBFdXRlbGVvc3RlaSIsIkFjYW50aG9wdGVyeWdpaSIpKSklPiUNCiAgYXJyYW5nZShoaWdoZXJfZ3JvdXAsb3JkZXIsdGF4b24pDQpsZXZlbF9pbmZvIDwtIHJlc2lkdWFsczIgJT4lDQogIGFycmFuZ2UoaGlnaGVyX2dyb3VwLCBvcmRlcikgJT4lIA0KICBwdWxsKG9yZGVyKSAlPiUgDQogIHVuaXF1ZSgpDQpyZXNpZHVhbHMyPC1yZXNpZHVhbHMyJT4lDQogIG11dGF0ZShvcmRlcj1mYWN0b3Iob3JkZXIsbGV2ZWxzPXJldihsZXZlbF9pbmZvKSkpDQpgYGANCg0KTm90ZSwgc29tZSBvZiB0aGUgY2xhZGVzIHVzZWQgaGVyZSAoaS5lLiwgIkJhc2FsIEFjdGlub3B0ZXJ5Z2lpIiwgIlN0ZW0gRXV0ZWxlb3N0ZWkiKSBhcmUgcGFyYXBoeWxldGljLCBidXQgdGhpcyB3YXMgZG9uZSBpbiBvcmRlciB0byBleGFtaW5lIHRoZSBtYWpvciB0cmVuZHMgaW4gdGhlIGV2b2x1dGlvbiBvZiBPT0wgcHJvcG9ydGlvbnMgYWNyb3NzIGZpc2hlcy4NCg0KKHJlZjpyZXNpZHVhbHNieW9yZGVyKSBCb3ggYW5kIHdoaXNrZXIgcGxvdCBvZiB0aGUgc3BlY2llcy1hdmVyYWdlIHJlc2lkdWFscyAoZXhjbHVkaW5nIGtub3duIGp1dmVuaWxlcykgb2YgdGhlIE9PTCB+IHRvdGFsIGxlbmd0aCByZWdyZXNzaW9uIG1vZGVsIGdyb3VwZWQgYnkgb3JkZXIsIHNvcnRlZCBhbmQgY29sb3ItY29kZWQgYnkgbWFqb3IgZmlzaCBjbGFkZS4NCg0KYGBge3IsZmlnLmFzcD0xLjUsZmlnLmNhcD0iKHJlZjpyZXNpZHVhbHNieW9yZGVyKSJ9DQpnZ3Bsb3QocmVzaWR1YWxzMiwNCiAgYWVzKHJlc2lkdWFscyx5PW9yZGVyKSkrDQogIGdlb21fdmxpbmUoeGludGVyY2VwdD0wLGxpbmV0eXBlPSJkYXNoZWQiKSsNCiAgZ2VvbV92aW9saW4oYWVzKGZpbGw9aGlnaGVyX2dyb3VwKSxzY2FsZT0id2lkdGgiKSsNCiAgbGFicyh5PSJIaWdoZXIgQ2xhZGUiLHg9IlJlc2lkdWFscyIsZmlsbD0iSGlnaGVyIENsYWRlIixjb2xvcj0iSGlnaGVyIENsYWRlIikrDQogIGdlb21fYm94cGxvdCgpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249YygwLjgsMC4zKSkNCmBgYA0KDQoocmVmOnJlc2lkdWFsc2J5b3JkZXIyKSBCb3ggYW5kIHdoaXNrZXIgcGxvdCBvZiB0aGUgc3BlY2llcy1hdmVyYWdlIHJlc2lkdWFscyAoZXhjbHVkaW5nIGtub3duIGp1dmVuaWxlcykgb2YgdGhlIE9PTCB+IHRvdGFsIGxlbmd0aCByZWdyZXNzaW9uIG1vZGVsLCBncm91cGVkIGJ5IG1ham9yIGZpc2ggY2xhZGUuDQoNCmBgYHtyLGZpZy5jYXA9IihyZWY6cmVzaWR1YWxzYnlvcmRlcjIpIn0NCmdncGxvdChyZXNpZHVhbHMyLGFlcyh5PXJlc2lkdWFscyx4PWhpZ2hlcl9ncm91cCkpKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9MCxsaW5ldHlwZT0iZGFzaGVkIikrDQogIGdlb21fdmlvbGluKGFlcyhmaWxsPWhpZ2hlcl9ncm91cCksc2NhbGU9IndpZHRoIikrDQogIGdlb21fYm94cGxvdCh3aWR0aD0wLjMpKw0KICBsYWJzKHg9IkhpZ2hlciBDbGFkZSIseT0iUmVzaWR1YWxzIikrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIsYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgdmp1c3QgPSAxLCBoanVzdD0xKSkNCmBgYA0KDQojIyBUZXN0aW5nIGlmIEFjYW50aG9wdGVyZ3lpaSBoYXZlIHN0YXRpc3RpY2FsbHkgbGFyZ2VyIGhlYWRzIHRoYW4gb3RoZXIgZmlzaGVzDQoNCiMjIyBUZXN0aW5nIHJlc2lkdWFscyBvZiBPT0wgdmVyc3VzIHRvdGFsIGxlbmd0aCB2YXJ5IHNpZ25pZmljYW50bHkgYmV0d2VlbiBhY2FudGhvcHRlcnlnaWFucyBhbmQgb3RoZXIgZmlzaGVzLg0KDQpgYGB7cn0NCnJlc2lkdWFsczIlPiUNCiAgbXV0YXRlKGhpZ2hlcl9ncm91cD1yZWxldmVsKGhpZ2hlcl9ncm91cCwiQ2hvbmRyaWNodGh5ZXMiKSklJCUNCiAgbG0ocmVzaWR1YWxzfmhpZ2hlcl9ncm91cCwuKSU+JQ0KICBzdW1tYXJ5KCkNCmBgYA0KDQpCYXNhbCBhY3Rpbm9wdGVyeWdpYW5zIGFuZCBhY2FudGhvcHRlcnlnaWFucyB3ZXJlIGZvdW5kIHRvIGhhdmUgc3RhdGlzdGljYWxseSBkZXRlY3RhYmxlIHJlc2lkdWFscyBmcm9tIHRoZSBiYXNlIGxldmVsIG9mIENob25kcmljaHRoeWVzLiBXaXRoIGJhc2FsIGFjdGlub3B0ZXJ5Z2lhbnMsIHRoaXMgaXMgbGlrZWx5IGR1ZSB0byB0aGUgbGFyZ2UgbnVtYmVyIG9mIGdhcnMgKExlcGlzb3N0ZWlkYWUpIGFuZCBlbG9uZ2F0ZS1ib2RpZWQgc3R1cmdlb25zIChpLmUuLCAqUHNldWRvc2NhcGhpcmh5bmNodXMqKSBjb25zaWRlcmVkIHJlbGF0aXZlIHRvIHRoZSBkaXZlcnNpdHkgb2YgdGhlIGNsYWRlLiBIb3dldmVyLCBmb3IgQWNhbnRob3B0ZXJ5Z2lpIHRoaXMgaXMgaGFyZGVyIHRvIGV4cGxhaW4gYmVjYXVzZSBhIHdpZGUgZGl2ZXJzaXR5IG9mIGFjYW50aG9wdGVyeWdpYW5zIHdlcmUgc2FtcGxlZC4NCg0KYGBge3J9DQpyZXNpZHVhbHMyJT4lDQogIG11dGF0ZShzaGFwZT1yZWxldmVsKGFzLmZhY3RvcihzaGFwZSksImZ1c2lmb3JtIiksDQogICAgICAgICBoaWdoZXJfZ3JvdXA9cmVsZXZlbChoaWdoZXJfZ3JvdXAsIkNob25kcmljaHRoeWVzIikpJSQlDQogIGxtKHJlc2lkdWFsc35oaWdoZXJfZ3JvdXArc2hhcGUsLiklPiUNCiAgc3VtbWFyeSgpDQpgYGANCg0KIyMjIyBTYW1lIGFuYWx5c2lzLCBleGFtaW5pbmcgaGVhZCBsZW5ndGgNCg0KKipDb25zaWRlcmluZyByZXNpZHVhbHMgb2YgaGVhZCBsZW5ndGggYW5kIHRvdGFsIGxlbmd0aCBieSB0aGVtc2VsdmVzKioNCg0KYGBge3J9DQpkYXRhX2ZpbmFsJT4lDQogIGRyb3BfbmEoaGVhZF9sZW5ndGgsdG90YWxfbGVuZ3RoKSU+JQ0KICBncm91cF9ieSh0YXhvbiklPiUNCiAgc3VtbWFyaXNlKGhpZ2hlcl9ncm91cD11bmlxdWUoaGlnaGVyX2dyb3VwKSxzaGFwZT11bmlxdWUoc2hhcGUpLA0KICAgICAgICAgICAgaGVhZF9sZW5ndGg9bWVhbihoZWFkX2xlbmd0aCksdG90YWxfbGVuZ3RoPW1lYW4odG90YWxfbGVuZ3RoKSklPiUNCiAgYXVnbWVudChsbShsb2codG90YWxfbGVuZ3RoKX5sb2coaGVhZF9sZW5ndGgpLC4pLG5ld2RhdGE9LiklPiUNCiAgbXV0YXRlKGhpZ2hlcl9ncm91cD1yZWxldmVsKGFzLmZhY3RvcihoaWdoZXJfZ3JvdXApLCJDaG9uZHJpY2h0aHllcyIpKSUkJQ0KICBsbSgucmVzaWR+aGlnaGVyX2dyb3VwLC4pJT4lDQogIHN1bW1hcnkoKQ0KYGBgDQoNCioqQWRkaW5nIGluIHNoYXBlIGFzIGFuIGFkZGl0aW9uYWwgY2F0ZWdvcmljYWwgdmFyaWFibGUqKg0KDQpgYGB7cn0NCmRhdGFfZmluYWwlPiUNCiAgZHJvcF9uYShoZWFkX2xlbmd0aCx0b3RhbF9sZW5ndGgpJT4lDQogIGdyb3VwX2J5KHRheG9uKSU+JQ0KICBzdW1tYXJpc2UoaGlnaGVyX2dyb3VwPXVuaXF1ZShoaWdoZXJfZ3JvdXApLHNoYXBlPXVuaXF1ZShzaGFwZSksDQogICAgICAgICAgICBoZWFkX2xlbmd0aD1tZWFuKGhlYWRfbGVuZ3RoKSx0b3RhbF9sZW5ndGg9bWVhbih0b3RhbF9sZW5ndGgpKSU+JQ0KICBhdWdtZW50KGxtKGxvZyh0b3RhbF9sZW5ndGgpfmxvZyhoZWFkX2xlbmd0aCkrc2hhcGUsLiksbmV3ZGF0YT0uKSU+JQ0KICBtdXRhdGUoaGlnaGVyX2dyb3VwPXJlbGV2ZWwoYXMuZmFjdG9yKGhpZ2hlcl9ncm91cCksIkNob25kcmljaHRoeWVzIikpJSQlDQogIGxtKC5yZXNpZH5oaWdoZXJfZ3JvdXAsLiklPiUNCiAgc3VtbWFyeSgpDQpgYGANCg0KSG93ZXZlciwgaW4gdGhlIGNhc2Ugb2YgaGVhZCBsZW5ndGggYWNhbnRob3B0ZXJ5Z2lhbnMgc3RpbGwgc3RhbmQgb3V0IGFzIGhhdmluZyBsYXJnZXIgdGhhbiBleHBlY3RlZCBoZWFkcyBldmVuIGFmdGVyIGFkZGluZyBpbiBzaGFwZSBhcyBhbiBhZGRpdGlvbmFsIGV4cGxhbmF0b3J5IHZhcmlhYmxlLiBUaGlzIGNvdWxkIGJlIGR1ZSB0byBwaHlsb2dlbmV0aWMgc2lnbmFsLCBhbmQgaXMgd29ydGggdGVzdGluZyB3aXRoIHBoeWxvZ2VuZXRpYyBjb21wYXJhdGl2ZSBtZXRob2RzIGluIGEgZnV0dXJlIGFuYWx5c2lzLCBidXQgaW4gdGhlIGNvbnRleHQgb2YgdGhlIHByZXNlbnQgc3R1ZHkgbWFrZXMgaXQgd29ydGggdGVzdGluZyBpZiBhY2FudGhvcHRlcnlnaWFuIGZpc2hlcyBoYXZlIGFuIHVuZHVlIGluZmx1ZW5jZSBvbiBsZW5ndGggcmVncmVzc2lvbnMgZm9yICpEdW5rbGVvc3RldXMqLg0KDQpOb3RhYmx5LCBQZXRyb215em9udGlmb3JtZXMgYWxzbyBoYXZlIGhlYWRzIHRoYXQgYXJlIGxhcmdlciB0aGFuIGV4cGVjdGVkIGZvciBhbmd1aWxsaWZvcm0gZmlzaGVzLiBUaGlzIG1heSBiZSBiZWNhdXNlIFBldHJvbXl6b250aWZvcm1lcyBhcmUgbm90IGFuZ3VpbGxpZm9ybSBiZWNhdXNlIG9mIHNwZWNpYWxpemF0aW9ucyBmb3IgYXhpYWwgZWxvbmdhdGlvbiwgYnV0IGJlY2F1c2UgdGhleSBkaXZlcmdlZCBmcm9tIG90aGVyIHZlcnRlYnJhdGVzIHByaW9yIHRvIHRoZSBldm9sdXRpb24gb2YgbGltYiBnaXJkbGVzLiBUaHVzLCBsYW1wcmV5cyBtYXkgbm90IGJlIHRydWx5IGFuZ3VpbGxpZm9ybSBidXQgImZ1c2lmb3JtIGZpc2hlcyB3aXRob3V0IGZpbnMiLg0KDQpBbHNvLCBub3RlIHRoYXQgaW4gdGhlc2UgYW5hbHlzZXMgYXJ0aHJvZGlyZXMgYXJlIHJlY292ZXJlZCBhcywgb24gYXZlcmFnZSwgbm90IGhhdmluZyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCBoZWFkLWJvZHkgcHJvcG9ydGlvbnMgZnJvbSBlbGFzbW9icmFuY2hzIGFuZCBvdGhlciBub24tYWNhbnRob3B0ZXJ5Z2lhbiBmaXNoZXMuDQoNCiMjIEFic29sdXRlIHJlc2lkdWFscyBvZiB0aGUgbW9kZWwgYnkgZmFtaWx5DQoNCihyZWY6ZmFtaWx5cmVzaWR1YWxzKSBCb3gtYW5kLXdoaXNrZXIgcGxvdCBvZiB0aGUgYWJzb2x1dGUgcmVzaWR1YWxzIG9mIHRoZSBtb2RlbCBzb3J0ZWQgYnkgZmFtaWx5LCBzaG93aW5nIGhvdyB0aGUgdGF4YSB3aXRoIHRoZSBsb3dlc3QgZXJyb3IgcmF0ZXMgdGVuZCB0byBiZSB0aG9zZSB3aXRoIGdlbmVyYWxpemVkLCBmdXNpZm9ybSBib2R5IHBsYW5zLg0KDQpgYGB7cixmaWcuYXNwPTQsZmlnLmhlaWdodD0xMSxmaWcuY2FwPSIocmVmOmZhbWlseXJlc2lkdWFscykifQ0KZ2dwbG90KHJlc2lkdWFsczIlPiUNCiAgICAgICAgIG11dGF0ZShhYnNfcmVzaWR1YWxzPWFicyhyZXNpZHVhbHMpKSwNCiAgICAgICBhZXMoeD1hYnNfcmVzaWR1YWxzLHk9cmVvcmRlcihmYW1pbHksZGVzYyhhYnNfcmVzaWR1YWxzKSkpKSsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PTAsbGluZXR5cGU9ImRhc2hlZCIpKw0KICBnZW9tX3Zpb2xpbihhZXMoZmlsbD1oaWdoZXJfZ3JvdXApLHNjYWxlPSJ3aWR0aCIpKw0KICBnZW9tX2JveHBsb3Qod2lkdGg9MC4zKSsNCiAgbGFicyh5PSJGYW1pbHkiLHg9IkFic29sdXRlIFJlc2lkdWFscyIpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiLGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMSwgaGp1c3Q9MSkpKw0KICBjb29yZF9jYXJ0ZXNpYW4oeGxpbT1jKDAsMC42KSkNCmBgYA0KDQojIEVmZmVjdCBvZiBib2R5IHNoYXBlIG9uIHJlZ3Jlc3Npb24gbW9kZWwNCg0KIyMgR3JhcGggb2YgT09MIHZlcnN1cyB0b3RhbCBsZW5ndGggY29sb3ItY29kZWQgYnkgc2hhcGUNCg0KKHJlZjpzY2F0dGVyYm9keXNoYXBlKSBHcmFwaCBvZiBPT0wgdmVyc3VzIHRvdGFsIGxlbmd0aCAocGxvdHRlZCBvbiBhIGxvZzxzdWI+MTA8L3N1Yj4gc2NhbGUpIGNvbG9yLWNvZGVkIGJ5IGJvZHkgc2hhcGUNCg0KYGBge3IsZmlnLmNhcD0iKHJlZjpzY2F0dGVyYm9keXNoYXBlKSJ9DQpkYXRhX2ZpbmFsJT4lDQogIGRyb3BfbmEoc2hhcGUpJT4lDQogIGdncGxvdChhZXMoeT10b3RhbF9sZW5ndGgseD1PT0wpKSsNCiAgZ2VvbV9zdGFyKGFlcyhmaWxsPXNoYXBlLHN0YXJzaGFwZT1jbGFkZSkpKw0KICBzY2FsZV9zdGFyc2hhcGVfbWFudWFsKHZhbHVlcz1jKDE1LDEzLDExLDEsMjgpLGd1aWRlPSJub25lIikrDQogIGdlb21fc21vb3RoKGZvcm11bGE9eX54LG1ldGhvZD0ibG0iKSsNCiAgZ2VvbV9zbW9vdGgoZm9ybXVsYT15fngsbWV0aG9kPSJsbSIpKw0KICBsYWJzKGZpbGw9IkJvZHkgU2hhcGUiLHN0YXJzaGFwZT0iQ2xhZGUiLA0KICAgICAgIHg9Ik9yYml0LU9wZXJjdWxhciBMZW5ndGggKGNtKSIseT0iVG90YWwgTGVuZ3RoIChjbSkiKSsNCiAgc2NhbGVfeF9jb250aW51b3VzKHRyYW5zPSdsb2cxMCcpKw0KICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnM9J2xvZzEwJykrDQogIHRoZW1lX2NsYXNzaWMoKSsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPWMoMC44NSwwLjI1KSkrDQogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc3RhcnNoYXBlID0gMTUpKSkNCmBgYA0KDQojIyBUZXN0aW5nIHRvIHNlZSBpZiBkaWZmZXJlbnQgYm9keSBzaGFwZXMgaGF2ZSBkaWZmZXJlbnQgcmVncmVzc2lvbiBsaW5lcw0KDQojIyMgVGVzdGluZyBmb3IgZGlmZmVyZW5jZXMgaW4gaW50ZXJjZXB0DQoNCmBgYHtyfQ0Kb3B0aW9ucyh3aWR0aD0xMDAwKQ0KI1Rlc3RpbmcgZm9yIGRpZmZlcmVuY2VzIGluIGludGVyY2VwdCBiZXR3ZWVuIGJvZHkgc2hhcGUgZ3JvdXBzDQpzdW1tYXJ5KGxtKGxvZyh0b3RhbF9sZW5ndGgpfmxvZyhPT0wpK3NoYXBlLA0KICAgICAgICAgICBkYXRhX2ZpbmFsKSkNCmBgYA0KDQojIyMgVGVzdGluZyBmb3IgZGlmZmVyZW5jZXMgaW4gc2xvcGUgYmV0d2VlbiBncm91cHMNCg0KYGBge3J9DQojVGVzdGluZyBmb3IgZGlmZmVyZW5jZXMgaW4gc2xvcGUgYmV0d2VlbiBib2R5IHNoYXBlIGdyb3Vwcw0Kc3VtbWFyeShsbShsb2codG90YWxfbGVuZ3RoKX5sb2coT09MKSpzaGFwZSwNCiAgICAgICAgICAgZGF0YV9maW5hbCkpDQpgYGANCg0KIyMjIEFOT1ZBIG9mIHJlc2lkdWFscyB2ZXJzdXMgYm9keSBzaGFwZQ0KDQpgYGB7cn0NCmRhdGFfZmluYWwlPiUNCiAgYnJvb206OmF1Z21lbnQoZml0Lk9PTCxuZXdkYXRhPS4pJT4lDQogIGxtKC5yZXNpZH5zaGFwZSwuKSU+JQ0KICBhbm92YSgpDQoNCmRhdGFfZmluYWwlPiUNCiAgYnJvb206OmF1Z21lbnQoZml0Lk9PTCxuZXdkYXRhPS4pJT4lDQogIGxtKC5yZXNpZH5zaGFwZSwuKSU+JSBzdW1tYXJ5KCkNCmBgYA0KDQojIyBCb3gtYW5kLXdoaXNrZXIgcGxvdCBvZiByZXNpZHVhbHMgYnkgYm9keSBzaGFwZQ0KDQoocmVmOnNoYXBlcmVzaWR1YWxzKSBCb3ggYW5kIHdoaXNrZXIgcGxvdCBvZiByZXNpZHVhbHMgaW4gdGhlIGRhdGFzZXQgY2F0ZWdvcml6ZWQgYnkgYm9keSBzaGFwZSwgc2hvd2luZyBob3cgZmlzaGVzIHdpdGggZWxvbmdhdGUgb3IgYW5ndWlsbGlmb3JtIGJvZGllcyBhcmUgY2hhcmFjdGVyaXplZCBieSBtb3JlIG5lZ2F0aXZlIHJlc2lkdWFscyBhbmQgZmlzaGVzIHdpdGggc2hvcnRlciAoY29tcHJlc3NpZm9ybSkgYm9kaWVzIGFyZSBjaGFyYWN0ZXJpemVkIGJ5IG1vcmUgcG9zaXRpdmUgcmVzaWR1YWxzLiBEYXRhIHBvaW50cyBhcmUgYXZlcmFnZSByZXNpZHVhbHMgZm9yIGVhY2ggc3BlY2llcyB3aXRoIGtub3duIGp1dmVuaWxlcyByZW1vdmVkLg0KDQpgYGB7cixmaWcuY2FwPSIocmVmOnNoYXBlcmVzaWR1YWxzKSIsY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZSd9DQpnZ3Bsb3QoT09MX3Jlc2lkdWFscyU+JQ0KICAgICAgICAgbXV0YXRlKHNoYXBlPWZhY3RvcihzaGFwZSxsZXZlbHM9YygibWFjcnVyaWZvcm0iLCJmbGF0dGVuZWQiLCJhbmd1aWxsaWZvcm0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZWxvbmdhdGUiLCJmdXNpZm9ybSIsImNvbXByZXNzaWZvcm0iKSkpICU+JQ0KICAgICAgICAgZmlsdGVyKG9udG9nZW55ICVpbiUgYygiYWR1bHQiLCJzdWJhZHVsdCIpfGlzLm5hKG9udG9nZW55KSkgJT4lDQogICAgICAgICBncm91cF9ieSh0YXhvbikgJT4lDQogICAgICAgICBzdW1tYXJpc2UocmVzaWR1YWxzPW1lYW4ocmVzaWR1YWxzKSxzaGFwZT11bmlxdWUoc2hhcGUpKSwNCiAgICAgICBhZXMoeD1zaGFwZSx5PXJlc2lkdWFscykpKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9MCxsaW5ldHlwZT0iZGFzaGVkIikrDQogIGdlb21fdmlvbGluKGFlcyhmaWxsPXNoYXBlKSxzY2FsZT0id2lkdGgiKSsNCiAgZ2VvbV9ib3hwbG90KHdpZHRoPTAuMykrDQogIGxhYnMoeD0iU2hhcGUiLHk9IlJlc2lkdWFscyIpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQ0KYGBgDQoNCmBgYHtyfQ0KT09MX3Jlc2lkdWFscyU+JQ0KICBmaWx0ZXIoc2hhcGU9PSJmdXNpZm9ybSIsIWZhbWlseSAlaW4lIGMoIkFjYW50aHVyaWRhZSIsIk1vbmFjYW50aGlkYWUiLCJCYWxpc3RpZGFlIikpJT4lDQogIG11dGF0ZShzaGFwZTI9ZmFjdG9yKGNhc2Vfd2hlbihmYW1pbHk9PSJIb2xvY2VudHJpZGFlIn4iSG9sb2NlbnRyaWRhZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGZhbWlseT09IlNlcnJhbmlkYWUifiJTZXJyYW5pZGFlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICJPdGhlciBGdXNpZm9ybSBGaXNoZXMiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgb3JkZXJlZD1ULGxldmVscz1jKCJIb2xvY2VudHJpZGFlIiwiU2VycmFuaWRhZSIsIk90aGVyIEZ1c2lmb3JtIEZpc2hlcyIpKSklPiUNCiAgZ3JvdXBfYnkoc2hhcGUyKSU+JQ0KICBzdW1tYXJpc2UobWVhbj1tZWFuKHJlc2lkdWFscyksDQogICAgICAgICAgICBtYXg9bWF4KHJlc2lkdWFscyksDQogICAgICAgICAgICBtaW49bWluKHJlc2lkdWFscyksDQogICAgICAgICAgICBsd3I9bWVhbi0yKnNkKHJlc2lkdWFscyksDQogICAgICAgICAgICB1cHI9bWVhbisyKnNkKHJlc2lkdWFscykpJT4lDQogIG11dGF0ZShDST1wYXN0ZTAoIigiLHJvdW5kKGx3ciwzKSwiLSIscm91bmQodXByLDMpLCIpIiksDQogICAgICAgICByYW5nZT1wYXN0ZTAoIigiLHJvdW5kKG1pbiwzKSwiLSIscm91bmQobWF4LDMpLCIpIikpJT4lDQogIHNlbGVjdChzaGFwZTIsbWVhbixDSSxyYW5nZSklPiUNCiAga2FibGUoZGlnaXRzPTMsDQogICAgICAgIGNhcHRpb249Ik1lYW4sIDk1JSBjb25maWRlbmNlIGludGVydmFsLCBhbmQgcmFuZ2Ugb2YgcmVzaWR1YWxzIGluIGZ1c2lmb3JtIGZpc2hlcywgc2hvd2luZyBob3cgaW4gZ2VuZXJhbCBmdXNpZm9ybSBmaXNoZXMgKGkuZS4sIG5vbi1ncm91cGVyIGZpc2hlcykgaGF2ZSByZXNpZHVhbHMgdGhhdCBzcGFuIDAuIiwNCiAgICAgICAgY29sLm5hbWVzPWMoIkdyb3VwIiwiTWVhbiIsIjk1JSBDLkkuIiwiUmFuZ2UiKSklPiUNCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KIyMgQ29tcGFyaW5nIHJlZ3Jlc3Npb24gbGluZXMgZm9yIGRpZmZlcmVudCBib2R5IHByb3BvcnRpb25zDQoNClRoZSBwcmltYXJ5IHB1cnBvc2Ugb2YgdGhpcyB3YXMgdG8gc2VlIGlmIGNvbXBsZXRlIGFydGhyb2RpcmVzIGNhbiByZWxpYWJseSBiZSBkZXNjcmliZWQgYXMgYGZ1c2lmb3JtYCBpbiBib2R5IHNoYXBlLCBnaXZlbiBhIGtub3duIGxlbmd0aCBhbmQgd2lkdGguDQoNCiMjIyBDb21wYXJpbmcgcmVncmVzc2lvbiBsaW5lcyBmb3IgdG90YWwgbGVuZ3RoIGFuZCBib2R5IGRlcHRoDQoNCmBgYHtyLGZpZy5jYXA9IlBsb3R0aW5nIHJlZ3Jlc3Npb24gbGluZXMgZm9yIGRpZmZlcmVudCBsZXZlbHMgb2YgYm9keSBzaGFwZSIsIGZpZy53aWR0aD03LGZpZy5hc3A9MC43NSx3YXJuaW5nPUZ9DQpkYXRhX2ZpbmFsJT4lDQogIGRyb3BfbmEoYm9keV9kZXB0aCklPiUNCiAgZ2dwbG90KGFlcyh0b3RhbF9sZW5ndGgsYm9keV9kZXB0aCkpKw0KICBnZW9tX3N0YXIoYWVzKGNvbG9yPXNoYXBlLHN0YXJzaGFwZT1jbGFkZSkpKw0KICBnZW9tX3N0YXIoYWVzKGZpbGw9c2hhcGUsc3RhcnNoYXBlPWNsYWRlKSkrDQogIHNjYWxlX3N0YXJzaGFwZV9tYW51YWwodmFsdWVzPWMoMTUsMTMsMjgsMSwxMSkpKw0KICBzY2FsZV94X2NvbnRpbnVvdXModHJhbnM9ImxvZzEwIikrDQogIHNjYWxlX3lfY29udGludW91cyh0cmFucz0ibG9nMTAiKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKGNvbG9yPXNoYXBlKSxtZXRob2Q9ImxtIixmb3JtdWxhPXl+eCxzZT1GKSsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSJsbSIsZm9ybXVsYT15fngpKw0KICBsYWJzKHk9IkJvZHkgRGVwdGggKGNtKSIseD0iVG90YWwgTGVuZ3RoIChjbSkiLA0KICAgICAgIGNvbG9yPSJCb2R5IFNoYXBlIixzdGFyc2hhcGU9IkNsYWRlIixmaWxsPSJCb2R5IFNoYXBlIikrDQogIGdlb21fc3RhcihkYXRhPS4lPiVmaWx0ZXIoY2xhZGU9PSJQbGFjb2Rlcm1pIiksDQogICAgICAgICAgICBhZXMoc3RhcnNoYXBlPWNsYWRlKSxjb2xvcj0id2hpdGUiLHN0YXJzdHJva2U9MC4zMyxzaXplPTMsZmlsbD0iYmxhY2siLHNob3cubGVnZW5kPUYpKw0KICBndWlkZXMoZmlsbD1ndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaGFwZSA9IDE1KSksDQogICAgICAgICBjb2xvcj1ndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaGFwZSA9IDE1KSkpDQpgYGANCg0KIyMjIENvbXBhcmluZyByZWdyZXNzaW9uIGxpbmVzIGZvciB0b3RhbCBsZW5ndGggYW5kIHByb3BvcnRpb25hbCBib2R5IGRlcHRoDQoNCmBgYHtyfQ0KZGF0YV9maW5hbCU+JQ0KICBkcm9wX25hKGJvZHlfZGVwdGgsdG90YWxfbGVuZ3RoKSU+JQ0KICBnZ3Bsb3QoYWVzKHg9dG90YWxfbGVuZ3RoLHk9Ym9keV9kZXB0aC90b3RhbF9sZW5ndGgpKSsNCiAgZ2VvbV9zdGFyKGFlcyhjb2xvcj1zaGFwZSxzdGFyc2hhcGU9Y2xhZGUpKSsNCiAgZ2VvbV9zdGFyKGFlcyhmaWxsPXNoYXBlLHN0YXJzaGFwZT1jbGFkZSkpKw0KICBnZW9tX3N0YXIoZGF0YT1kYXRhX2ZpbmFsJT4lZmlsdGVyKGNsYWRlPT0iUGxhY29kZXJtaSIpLA0KICAgICAgICAgICAgYWVzKHN0YXJzaGFwZT1jbGFkZSksY29sb3I9IndoaXRlIixzdGFyc3Ryb2tlPTAuMzMsc2l6ZT0zLGZpbGw9ImJsYWNrIixzaG93LmxlZ2VuZD1GKSsNCiAgc2NhbGVfeF9jb250aW51b3VzKHRyYW5zPSJsb2cxMCIpKw0KICBzY2FsZV9zdGFyc2hhcGVfbWFudWFsKHZhbHVlcz1jKDE1LDEzLDExLDEsMjgpKSsNCiAgbGFicyh5PSJCb2R5IERlcHRoIChhcyAlIG9mIHRvdGFsIGxlbmd0aCkiLHg9IlRvdGFsIExlbmd0aCAoY20pIiwNCiAgICAgICBzdGFyc2hhcGU9IkNsYWRlIixjb2xvcj0iQm9keSBTaGFwZSIsZmlsbD0iQm9keSBTaGFwZSIpKw0KICBnZW9tX3Ntb290aChhZXMoY29sb3I9c2hhcGUpLG1ldGhvZD0ibG0iLGZvcm11bGE9eX54LHNlPUYpKw0KICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIixmb3JtdWxhPXl+eCkrDQogIGd1aWRlcyhmaWxsPWd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNoYXBlID0gMTUpKSwNCiAgICAgICAgIGNvbG9yPWd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNoYXBlID0gMTUpKSkNCmBgYA0KDQojIyMgQ29tcGFyaW5nIHRvdGFsIGxlbmd0aCBhZ2FpbnN0IGJvZHkgd2lkdGgNCg0KYGBge3Isd2FybmluZz1GfQ0KZGF0YV9maW5hbCU+JQ0KICBmaWx0ZXIoY2xhZGUgJWluJSBjKCJBY3Rpbm9wdGVyeWdpaSIsIkNob25kcmljaHRoeWVzIiwiUGxhY29kZXJtaSIsIlNhcmNvcHRlcnlnaWkiKSkgJT4lDQogIGRyb3BfbmEoYm9keV93aWR0aCklPiUNCiAgZ2dwbG90KGFlcyh4PXRvdGFsX2xlbmd0aCx5PWJvZHlfd2lkdGgpKSsNCiAgbGFicyh5PSJCb2R5IFdpZHRoIChjbSkiLHg9IlRvdGFsIExlbmd0aCAoY20pIiwNCiAgICAgICBjb2xvcj0iU2hhcGUiLGZpbGw9IlNoYXBlIixzdGFyc2hhcGU9IkNsYWRlIikrDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yPXNoYXBlKSxzaGFwZT0yMSkrDQogIGdlb21fc3RhcihhZXMoZmlsbD1zaGFwZSxzdGFyc2hhcGU9Y2xhZGUpKSsNCiAgc2NhbGVfeF9jb250aW51b3VzKHRyYW5zPSdsb2cxMCcpKw0KICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnM9J2xvZzEwJykrDQogIHNjYWxlX3N0YXJzaGFwZV9tYW51YWwodmFsdWVzPWMoMTUsMTMsMSwyOCkpKw0KICBnZW9tX3Ntb290aChhZXMoY29sb3I9c2hhcGUpLG1ldGhvZD0ibG0iLGZvcm11bGE9eX54LHNlPUYpKw0KICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIixmb3JtdWxhPXl+eCkrDQogIGdlb21fc3RhcihkYXRhPWRhdGFfZmluYWwlPiVmaWx0ZXIoY2xhZGU9PSJQbGFjb2Rlcm1pIiksc2l6ZT0zLGZpbGw9IndoaXRlIikrDQogIHRoZW1lX2NsYXNzaWMoKSsNCiAgZ3VpZGVzKGZpbGw9Z3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2hhcGUgPSAxNSkpKQ0KYGBgDQoNCkJhc2VkIG9uIHRoaXMsIGl0IGFwcGVhcnMgdGhhdCBhcnRocm9kaXJlcyBrbm93biBmcm9tIGNvbXBsZXRlIHJlbWFpbnMgc2hvdyBhIHNoYXBlIHNpbWlsYXIgZW5vdWdoIHRvIGZ1c2lmb3JtIGZpc2hlcyB0byBjYXRlZ29yaXplIHRoZW0gYXMgc3VjaCAoW3dpdGggdGhlIHBvc3NpYmxlIGV4Y2VwdGlvbiBvZiAqQW1hemljaHRoeXMqXSgjQW1hemljaHRoeXMpKS4gSG93ZXZlciwgYW1vbmcgZnVzaWZvcm0gZmlzaGVzIGFydGhyb2RpcmVzIHNob3cgYXNwZWN0cyBvZiBib2R5IHNoYXBlIHRoYXQgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnRpYXRlIHRoZW0gZnJvbSBib3RoIGFjdGlub3B0ZXJ5Z2lhbnMgYW5kIGVsYXNtb2JyYW5jaHMgKHNlZSBuZXh0IHNlY3Rpb24pLg0KDQojIyBCb2R5IHNoYXBlIGRpdmVyc2l0eSBpbiBmaXNoZXMNCg0KIyMjIEJvZHkgaGVpZ2h0IHZlcnN1cyBib2R5IHdpZHRoDQoNCkZvciB0aGlzIHNlY3Rpb24sIHRvdGFsIGxlbmd0aCBmb3IgYXJ0aHJvZGlyZXMgZm9yIHdoaWNoIGluY29tcGxldGUgcmVtYWlucyBhcmUga25vd24gd2FzIGVzdGltYXRlZCB1c2luZyBbdGhlIG1vZGVsIGluY2x1ZGluZyBib2R5IHNoYXBlIGFzIGEgY292YXJpYXRlIGFuZCBhbGxvd2luZyBzbG9wZSB0byB2YXJ5IGJldHdlZW4gQ2hvbmRyaWNodGh5YW5zIGFuZCBhbGwgb3RoZXIgZmlzaGVzXSgjZml0c2hhcGVjbGFkZSksIHdoaWNoIGlzIGNvbnNpZGVyZWQgdGhlIGJlc3QtZml0dGluZyBlcXVhdGlvbiBoZXJlLCBpbiBvcmRlciB0byBoaWdobGlnaHQgZGlmZmVyZW5jZXMgaW4gb3ZlcmFsbCBib2R5IHNoYXBlIGJldHdlZW4gYXJ0aHJvZGlyZXMgYW5kIGFsbCBvdGhlciBmaXNoZXMuDQoNCihyZWY6Ym9keWhlaWdodHdpZHRoKSBEb3R0ZWQgbGluZSByZXByZXNlbnRzIGEgbGluZSB3aXRoIHNsb3BlIG9mIDEgKGkuZS4sIHJlcHJlc2VudGluZyBmaXNoZXMgd2l0aCBhIGJvZHkgZXF1YWwgaW4gaGVpZ2h0IGFuZCB3aWR0aCksIHNob3dpbmcgaG93IGFydGhyb2RpcmVzIGFuZCBjaG9uZHJpY2h0aHlhbnMgZ2VuZXJhbGx5IGhhdmUgYm9kaWVzIHRoYXQgYXJlIHN1YmNpcmN1bGFyIGluIGNyb3NzLXNlY3Rpb24sIHdoZXJlYXMgYWN0aW5vcHRlcnlnaWFucyBnZW5lcmFsbHkgaGF2ZSBib2RpZXMgdGhhdCBhcmUgbXVjaCBkZWVwZXIgdGhhbiB3aWRlDQoNCmBgYHtyLGZpZy5jYXA9IihyZWY6Ym9keWhlaWdodHdpZHRoKSJ9DQpnZ3Bsb3QoZGF0YV9maW5hbCAlPiUgDQogICAgICAgICBkcm9wX25hKGJvZHlfZGVwdGgsYm9keV93aWR0aCklPiUNCiAgICAgICAgIGZpbHRlcihjbGFkZSAlaW4lDQogICAgICAgICAgICAgICAgICAgICAgICAgIGMoIkFjdGlub3B0ZXJ5Z2lpIiwiQ2hvbmRyaWNodGh5ZXMiLCJQbGFjb2Rlcm1pIiwiU2FyY29wdGVyeWdpaSIpKSwNCiAgICAgICBhZXMoYm9keV9kZXB0aCxib2R5X3dpZHRoKSkrDQogIGdlb21fc3RhcihhZXMoZmlsbD1jbGFkZSxzdGFyc2hhcGU9Y2xhZGUpLHNpemU9MS41KSsNCiAgZ2VvbV9zdGFyKGRhdGE9LiU+JWZpbHRlcihjbGFkZT09IlBsYWNvZGVybWkiKSxhZXMoZmlsbD1jbGFkZSxzdGFyc2hhcGU9Y2xhZGUpLHNpemU9MywNCiAgICAgICAgICAgIHNob3cubGVnZW5kPUYpKw0KICBzY2FsZV9zdGFyc2hhcGVfbWFudWFsKHZhbHVlcz1jKDE1LDEzLDEsMjgpKSsNCiAgc2NhbGVfeF9jb250aW51b3VzKHRyYW5zPSJsb2cxMCIpKw0KICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnM9ImxvZzEwIikrDQogIGdlb21fYWJsaW5lKHNsb3BlPTEsbGluZXR5cGU9ImRhc2hlZCIpKw0KICBsYWJzKHg9IkJvZHkgSGVpZ2h0IChjbSkiLHk9IkJvZHkgV2lkdGggKGNtKSIsY29sb3I9IkNsYWRlIixmaWxsPSJDbGFkZSIsc3RhcnNoYXBlPSJDbGFkZSIpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249YygwLjgsMC4yKSkNCmBgYA0KDQpBcyBjYW4gYmUgc2VlbiBmcm9tIHRoaXMgZ3JhcGgsIGNob25kcmljaHRoeWFucywgYXJ0aHJvZGlyZXMsIGFuZCB3aGF0IGZldyBzYXJjb3B0ZXJ5Z2lhbnMgY291bGQgYmUgY29uc2lkZXJlZCBpbiB0aGlzIGFuYWx5c2lzIGdlbmVyYWxseSBoYXZlIGEgYm9keSB0aGF0IGlzIHN1YmNpcmN1bGFyIGluIGNyb3NzLXNlY3Rpb24gKHdpZHRoIGFuZCBoZWlnaHQgbmVhciBlcXVhbCksIHdoZXJlYXMgbW9zdCBhY3Rpbm9wdGVyeWdpYW5zIGhhdmUgYSBib2R5IHRoYXQgaXMgbXVjaCBkZWVwZXIgcmVsYXRpdmUgdG8gaXRzIHdpZHRoLg0KDQoNCiMjIyBCb3hwbG90IG9mIGBib2R5X2RlcHRoYC9gYm9keV93aWR0aGAgYWNyb3NzIGZpc2hlcw0KDQpgYGB7cixmaWcuY2FwPSJCb3ggcGxvdCBvZiBib2R5IGRlcHRoL2JvZHkgd2lkdGggcmF0aW8gaW4gZmlzaGVzIGV4YW1pbmVkIGluIHRoaXMgc3R1ZHkifQ0KZGF0YV9maW5hbCU+JQ0KICBkcm9wX25hKGJvZHlfd2lkdGgsYm9keV9kZXB0aCklPiUNCiAgbXV0YXRlKGNsYWRlMj1jYXNlX3doZW4oZ2VudXMgJWluJSBjKCJEdW5rbGVvc3RldXMiLCJQYXJhbXlsb3N0b21hIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJCdW5nYXJ0aXVzIiwiSGVpbnR6aWNodGh5cyIpIH4gIkNsZXZlbGFuZCBTaGFsZSBBcnRocm9kaXJhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhZGU9PSJQbGFjb2Rlcm1pIn4iT3RoZXIgQXJ0aHJvZGlyYSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGdlbnVzICVpbiUgYygiVGh1bm51cyIsIkthdHN1d29udXMiLCJBbGxvdGh1bm51cyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQXV4aXMiLCJFdXRoeW5udXMiLCJTYXJkYSIsIkN5Ymlvc2FyZGEiLCJHeW1ub3NhcmRhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJPcmN5bm9wc2lzIikgfiAiVGh1bm5pbmkgYW5kIFNhcmRpbmkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBvcmRlcj09IlNpbHVyaWZvcm1lcyIgfiAiU2lsdXJpZm9ybWVzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgaGlnaGVyX2dyb3VwPT0iQmFzYWwgQWN0aW5vcHRlcnlnaWkifiJCYXNhbCBBY3Rpbm9wdGVyeWdpaSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGNsYWRlPT0iQWN0aW5vcHRlcnlnaWkiIH4gIk90aGVyIFRlbGVvc3RlaSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGNsYWRlPT0iQ2hvbmRyaWNodGh5ZXMiIH4gIkNob25kcmljaHRoeWVzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhZGUgPT0gIlNhcmNvcHRlcnlnaWkiIH4gIlNhcmNvcHRlcnlnaWkiKSkgJT4lDQogIG11dGF0ZShib2R5X3JhdGlvPWJvZHlfZGVwdGgvYm9keV93aWR0aCwNCiAgICAgICAgIGNsYWRlMj1mYWN0b3IoY2xhZGUyLG9yZGVyZWQ9VCwNCiAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzPWMoIkNsZXZlbGFuZCBTaGFsZSBBcnRocm9kaXJhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk90aGVyIEFydGhyb2RpcmEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ2hvbmRyaWNodGh5ZXMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQmFzYWwgQWN0aW5vcHRlcnlnaWkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiU2lsdXJpZm9ybWVzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlRodW5uaW5pIGFuZCBTYXJkaW5pIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk90aGVyIFRlbGVvc3RlaSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJTYXJjb3B0ZXJ5Z2lpIikpKSAlPiUNCiAgZ2dwbG90KGFlcyhjbGFkZTIsYm9keV9yYXRpbykpKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9MSxsaW5ldHlwZT0iZGFzaGVkIikrDQogIGdlb21fdmlvbGluKGFlcyhmaWxsPWNsYWRlMiksc2NhbGU9IndpZHRoIixzaG93LmxlZ2VuZD1GKSsNCiAgZ2VvbV9ib3hwbG90KHdpZHRoPTAuMykrDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKGh1ZV9wYWwoKSg3KSkpKw0KICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IHdyYXBfZm9ybWF0KDE2KSkrDQogIGxhYnMoeT0iQm9keSBEZXB0aC9Cb2R5IFdpZHRoIix4PSJDbGFkZSIpKw0KICBjb29yZF9jYXJ0ZXNpYW4oeWxpbT1jKDAsNCkpKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LHZqdXN0ID0gMSwgaGp1c3Q9MSkpDQpgYGANCg0KVGhlIG91dGxpZXIgcG9pbnQgYW1vbmcgIk90aGVyIEFydGhyb2RpcmEiIGlzICpFYXN0bWFub3N0ZXVzIGNhbGxpYXNwaXMqLCBhIHBhY2h5b3N0ZW9tb3JwaCBhcnRocm9kaXJlIHRoYXQgc2hvd3MgYSBtZWRpb2xhdGVyYWxseSBuYXJyb3dlciBib2R5IHRoYW4gb3RoZXIgdGF4YS4gVGhlIENsZXZlbGFuZCBTaGFsZSBhcnRocm9kaXJlIHRoYXQgcmVwcmVzZW50cyBhbiBvdXRsaWVyIGlzIENNTkggNzQyNCwgYSBqdXZlbmlsZSBzcGVjaW1lbiBvZiAqRC4gdGVycmVsbGkqIHdpdGggYSBtdWNoIG1vcmUgY2lyY3VsYXIgYm9keSBjcm9zcy1zZWN0aW9uLCBtb3JlIHNpbWlsYXIgdG8gY29jY29zdGVvbW9ycGhzLiBOb3RlIHRoYXQgYWxsIG9mIHRoZSAiT3RoZXIgQXJ0aHJvZGlyZXMiIGNvbnNpZGVyZWQgaGVyZSBhcmUgbm9uLXBhY2h5b3N0ZW9tb3JwaCB0YXhhLiAqQW1hemljaHRoeXMgdHJpbmFqc3RpY2FlKiBjb3VsZCBub3QgYmUgaW5jbHVkZWQgYXMgaXRzIGBib2R5X3dpZHRoYCBpcyB1bmNsZWFyLg0KDQojIyMgQm9keSBoZWlnaHQgdmVyc3VzIHRvdGFsIGxlbmd0aCBpbiBmdXNpZm9ybSBmaXNoZXMNCg0KKHJlZjpib2R5ZGVwdGhmdXNpZm9ybSkgUGxvdCBvZiBib2R5IGRlcHRoIHZlcnN1cyB0b3RhbCBsZW5ndGggaW4gZnVzaWZvcm0gZmlzaGVzLiBOb3RlIGhvdyBhcnRocm9kaXJlcyBhbmQgc2FyY29wdGVyeWdpYW5zIGhhdmUgZGVlcGVyIGJvZGllcyB0aGFuIGNob25kcmljaHRoeWFucyBkZXNwaXRlIGxhY2tpbmcgdGhlIHZlcnkgbWVkaW9sYXRlcmFsbHkgbmFycm93IGJvZGllcyBzZWVuIGluIGFjdGlub3B0ZXJ5Z2lhbnMsIGFuZCBpbmRlZWQgYWN0aW5vcHRlcnlnaWFucyBvZnRlbiBoYXZlIGRlZXBlciBib2RpZXMgdGhhbiBzaW1pbGFyLXNpemVkIGFydGhyb2RpcmVzIGV4Y2VwdCBpbiB0aGUgdmVyeSBkZWVwLWJvZGllZCAqRHVua2xlb3N0ZXVzKg0KDQpgYGB7cixmaWcuY2FwPSIocmVmOmJvZHlkZXB0aGZ1c2lmb3JtKSJ9DQpmaXQuc2hhcGVfY2xhZGUzPC1sbShsb2codG90YWxfbGVuZ3RoKX5sb2coT09MKSooY2xhZGU9PSJDaG9uZHJpY2h0aHllcyIpKw0KICAgICAgICAgICAgICAgICAgICAgICBzaGFwZSsoZmFtaWx5PT0iU2VycmFuaWRhZSIpKyhmYW1pbHk9PSJIb2xvY2VudHJpZGFlIiksDQogICAgICAgICAgICAgICAgICAgIGRhdGFfZmluYWwpDQoNCmRhdGFfZmluYWwgJT4lDQogIGZpbHRlcighaXMubmEoYm9keV9kZXB0aCksc2hhcGU9PSJmdXNpZm9ybSJ8Y2xhZGU9PSJQbGFjb2Rlcm1pIiklPiUNCiAgbXV0YXRlKHRvdGFsX2xlbmd0aD1pZmVsc2UoaXMubmEodG90YWxfbGVuZ3RoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXhwKHByZWRpY3QoZml0LnNoYXBlX2NsYWRlMywuKSkqDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQuc2hhcGVfY2xhZGUzKSRDRiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG90YWxfbGVuZ3RoKSklPiUNCiAgICAgICAgIGFycmFuZ2UoY2xhZGUpJT4lDQogIGdncGxvdChhZXModG90YWxfbGVuZ3RoLGJvZHlfZGVwdGgpKSsNCiAgZ2VvbV9zdGFyKGFlcyhmaWxsPWNsYWRlLHN0YXJzaGFwZT1jbGFkZSksc2l6ZT0xLjUpKw0KICBzY2FsZV9zdGFyc2hhcGVfbWFudWFsKHZhbHVlcz1jKDE1LDEzLDEsMjgpKSsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSJsbSIsZm9ybXVsYT15fngsYWVzKGNvbG9yPWNsYWRlKSxzZT1GKSsNCiAgZ2VvbV9zdGFyKGRhdGE9LiU+JWZpbHRlcihjbGFkZT09IlBsYWNvZGVybWkiKSxhZXMoZmlsbD1jbGFkZSxzdGFyc2hhcGU9Y2xhZGUpLHNpemU9MywNCiAgICAgICAgICAgIHNob3cubGVnZW5kPUYpKw0KICBzY2FsZV94X2NvbnRpbnVvdXModHJhbnM9ImxvZzEwIikrDQogIHNjYWxlX3lfY29udGludW91cyh0cmFucz0ibG9nMTAiKSsNCiAgbGFicyh4PSJUb3RhbCBMZW5ndGggKGNtKSIseT0iQm9keSBEZXB0aCAoY20pIixjb2xvcj0iQ2xhZGUiLA0KICAgICAgIGZpbGw9IkNsYWRlIixzdGFyc2hhcGU9IkNsYWRlIikrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj1jKDAuOCwwLjIpKQ0KYGBgDQoNCkhvd2V2ZXIsIGFydGhyb2RpcmVzIGhhdmUgbXVjaCBkZWVwZXIgYm9kaWVzIHRoYW4gc2hhcmtzIGF0IHNpbWlsYXIgYm9keSBsZW5ndGhzLCBtb3JlIGNvbXBhcmFibGUgdG8gYWN0aW5vcHRlcnlnaWFuIGFuZCBzYXJjb3B0ZXJ5Z2lhbiBmaXNoZXMuIFRoaXMgaW5jbHVkZXMgdGhlIG90aGVyd2lzZSB2ZXJ5IHNoYXJrLWxpa2UgKkFtYXppY2h0aHlzIHRyaW5hanN0aWNhZSouIFRoZXJlIGFyZSBvbmx5IHR3byB0YXhhIHdoaWNoIGRvIG5vdC4gVGhlIGZpcnN0IGlzICpHeW1ub3RyYWNoZWx1cyBoeWRlaSosIHdoaWNoIGhhcyBhIHZlcnkgdW51c3VhbCBhcm1vciBzaGFwZSAobmFycm93IGJvZHksIGFudGVyb3Bvc3Rlcmlvcmx5IHNob3J0IGFybW9yIHJlbGF0aXZlIHRvIHRoZSBoZWFkKSB0aGF0IHN1Z2dlc3RzIGl0IG1heSBldmVudHVhbGx5IGJlIGZvdW5kIG91dCB0byBwZXJ0YWluIHRvIGEgaGlnaGx5IGVsb25nYXRlLWJvZGllZCBvciBhbmd1aWxsaWZvcm0gdGF4b24uIFRoZSBvdGhlciBpcyB0aGUgcmVjb25zdHJ1Y3Rpb24gb2YgKlRpdGFuaWNodGh5cyBjbGFya2kqIGluIEJveWxlIGFuZCBSeWFuICgyMDE3KS4gSG93ZXZlciwgdGhlIHZhbHVlIGZvciB0aGlzIHRheG9uIG1heSBiZSBhbiB1bmRlcmVzdGltYXRlIGJlY2F1c2UuLi4NCg0KIDEuIFRoZSByZWNvbnN0cnVjdGVkIHNwZWNpbWVuIGlzIGEgc3ViYWR1bHQsIGFuZCBib2R5IGRlcHRoIGluY3JlYXNlcyB0aHJvdWdoIG9udG9nZW55IGluICpEdW5rbGVvc3RldXMgdGVycmVsbGkqDQogMi4gVGhlIHJlY29uc3RydWN0aW9uIGlzIG1pc3NpbmcgaXRzIHZlbnRyYWwgc2hpZWxkIChib2R5IGhlaWdodCBoZXJlIGlzIGFuIGFwcHJveGltYXRpb24pLCBhbmQgdGh1cyB0aGUgYWRkaXRpb24gb2YgdGhlIHZlbnRyYWwgYXJtb3IgbWF5IG1ha2UgdGhlIGJvZHkgZGVlcGVyLg0KIDMuIFRoZSByZWNvbnN0cnVjdGVkIGhlaWdodCBvZiB0aGUgdG9yc28gbWF5IGJlIGFuIHVuZGVyZXN0aW1hdGUsIGFzIHRoZSBtZWRpb2RvcnNhbCBzZWVtcyB0byBiZSB0b28gbG93IHRvIGFsbG93IHRoZSBoZWFkLXJhaXNpbmcgbXVzY3VsYXR1cmUgdG8gZWFzaWx5IGF0dGFjaCB0byBpdCBhbmQgYWxzbyBtYWludGFpbiBhIHN0cmVhbWxpbmVkIGJvZHkgc2hhcGUuDQoNCiMjIyBCb2R5IHdpZHRoIHZlcnN1cyB0b3RhbCBsZW5ndGggaW4gZnVzaWZvcm0vZWxvbmdhdGUgZmlzaGVzDQoNCihyZWY6bGVuZ3Rod2lkdGgpIFBsb3Qgb2YgYm9keSB3aWR0aCB2ZXJzdXMgdG90YWwgbGVuZ3RoIGluIGZ1c2lmb3JtIGFuZCBlbG9uZ2F0ZS1ib2RpZWQgZmlzaGVzLiBUb3RhbCBsZW5ndGggaXMgZXN0aW1hdGVkIGZvciBhcnRocm9kaXJlcyBmb3Igd2hpY2ggY29tcGxldGUgcmVtYWlucyBhcmUgdW5rbm93bi4NCg0KYGBge3IsZmlnLmNhcD0iKHJlZjpsZW5ndGh3aWR0aCkifQ0KZ2dwbG90KGRhdGFfZmluYWwgJT4lIGZpbHRlcighaXMubmEoYm9keV93aWR0aCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNoYXBlICVpbiUgYygiZWxvbmdhdGUiLCJmdXNpZm9ybSIpKSU+JQ0KICAgICAgICAgbXV0YXRlKHRvdGFsX2xlbmd0aD1pZmVsc2UoaXMubmEodG90YWxfbGVuZ3RoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV4cChwcmVkaWN0KGZpdC5zaGFwZV9jbGFkZTMsLikpKg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKGZpdC5zaGFwZV9jbGFkZTMpJENGLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG90YWxfbGVuZ3RoKSwNCiAgICAgICAgICAgICAgICBjbGFkZT1pZmVsc2Uob3JkZXIgJWluJSBjKCJTaWx1cmlmb3JtZXMiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkFjdGlub3B0ZXJ5Z2lpIChTaWx1cmlmb3JtZXMpIixjbGFkZSksDQogICAgICAgICAgICAgICAgY2xhZGU9aWZlbHNlKGZhbWlseSAlaW4lIGMoIlNjb21icmlkYWUiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkFjdGlub3B0ZXJ5Z2lpIChTY29tYnJpZGFlKSIsY2xhZGUpKSU+JQ0KICAgICAgICAgYXJyYW5nZShjbGFkZSksDQogICAgICAgYWVzKHRvdGFsX2xlbmd0aCxib2R5X3dpZHRoKSkrDQogIGdlb21fc21vb3RoKG1ldGhvZD0ibG0iLGZvcm11bGE9eX54LGFlcyhjb2xvcj1jbGFkZSksc2U9RikrDQogIGdlb21fc3RhcihhZXMoY29sb3I9Y2xhZGUsc3RhcnNoYXBlPWNsYWRlKSwNCiAgICAgICAgICAgIC4lPiVmaWx0ZXIoY2xhZGU9PSJTYXJjb3B0ZXJ5Z2lpIiksc2l6ZT0xLjUpKw0KICBnZW9tX3N0YXIoYWVzKGZpbGw9Y2xhZGUsc3RhcnNoYXBlPWNsYWRlKSxzaXplPTEuNSkrDQogIHNjYWxlX3N0YXJzaGFwZV9tYW51YWwodmFsdWVzPWMoMTUsMTUsMTUsMTMsMSwyOCkpKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoaHVlX3BhbCgpKDMpWzFdLCIjYzQzNDJhIiwiI2ZiYjJhZCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGh1ZV9wYWwoKSgzKVsyXSwiYmxhY2siLGh1ZV9wYWwoKSgzKVszXSkpKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YyhodWVfcGFsKCkoMylbMV0sIiNjNDM0MmEiLCIjZmJiMmFkIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaHVlX3BhbCgpKDMpWzJdLCJibGFjayIsaHVlX3BhbCgpKDMpWzNdKSkrDQogIGdlb21fc21vb3RoKG1ldGhvZD0ibG0iLGZvcm11bGE9eX54LGFlcyhjb2xvcj1jbGFkZSksc2U9RixzaG93LmxlZ2VuZD1GKSsNCiAgZ2VvbV9zdGFyKGRhdGE9LiU+JWZpbHRlcihjbGFkZT09IlBsYWNvZGVybWkiKSxhZXMoZmlsbD1jbGFkZSxzdGFyc2hhcGU9Y2xhZGUpLA0KICAgICAgICAgICAgY29sb3I9IndoaXRlIixzdGFyc3Ryb2tlPTAuMzMsc2l6ZT0zLHNob3cubGVnZW5kPUYpKw0KICBzY2FsZV94X2NvbnRpbnVvdXModHJhbnM9ImxvZzEwIikrDQogIHNjYWxlX3lfY29udGludW91cyh0cmFucz0ibG9nMTAiKSsNCiAgbGFicyh4PSJUb3RhbCBMZW5ndGggKGNtKSIseT0iQm9keSBXaWR0aCAoY20pIixjb2xvcj0iQ2xhZGUiLA0KICAgICAgIGZpbGw9IkNsYWRlIixzdGFyc2hhcGU9IkNsYWRlIixhbHBoYT0iQ2xhZGUiKSsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPWMoMC44LDAuMikpDQpgYGANCg0KQmFzZWQgb24gdGhpcywgaXQgaXMgY2xlYXIgdGhhdCBhcnRocm9kaXJlcyBoYXZlIG11Y2ggd2lkZXIgYm9kaWVzIHRoYW4gbW9zdCBjaG9uZHJpY2h0aHlhbnMgb3IgdHlwaWNhbCBhY3Rpbm9wdGVyeWdpYW5zLiBUaGUgb25seSBmaXNoZXMgdGhhdCBoYXZlIHNpbWlsYXIgYm9keSBwcm9wb3J0aW9ucyB0byBhcnRocm9kaXJlcyBpbiB0aGlzIHJlZ2FyZCBhcmUgY2F0ZmlzaGVzIChTaWx1cmlmb3JtZXMpLCB0dW5hLWxpa2Ugc2NvbWJyaWRzIChpLmUuLCB0YXhhIGxpa2UgKlRodW5udXMqIG9yICpTYXJkYSopLCBhbmQgc2FyY29wdGVyeWdpYW5zIChub3Qgc2hvd24gaW4gZmlndXJlIGR1ZSB0byBsb3cgbnVtYmVyIG9mIGRhdGEgcG9pbnRzKS4NCg0KIyMgQ29ycmVsYXRpb24gYmV0d2VlbiBhc3BlY3QgcmF0aW8gYW5kIHBlcmNlbnQgaGVhZCBsZW5ndGgNCg0KKHJlZjphc3BlY3RyYXRpbykgUGxvdCBvZiAoKipBKiopIHBlcmNlbnQgaGVhZCBsZW5ndGggdmVyc3VzIGJvZHkgYXNwZWN0IHJhdGlvIChgYm9keSBkZXB0aGAvYHByZWNhdWRhbCBsZW5ndGhgKSBhbmQgKCoqQioqKSBwbG90IG9mIHBlcmNlbnQgT09MIHZlcnN1cyBib2R5IGFzcGVjdCByYXRpbywgc2hvd2luZyBob3cgZm9yIHRoZSBtb3N0IHBhcnQgaGVhZCBwcm9wb3J0aW9ucyBhcmUgY29uc2lzdGVudCBpbiBmaXNoZXMgb2YgZGlmZmVyZW50IGJvZHkgc2hhcGVzIGJ1dCB0aGVyZSBpcyBhIHNsaWdodCBlZmZlY3Qgb2YgYXhpYWwgZWxvbmdhdGlvbi4gQmxhY2sgY2lyY2xlcyBhcmUgZ3JvdXBlcnMgKFNlcnJhbmlkYWUpIGFuZCB3aGl0ZSBjaXJjbGVzIGFyZSB0YXhhIHdpdGggcG9zdGVyaW9ybHkgc2hpZnRlZCBvcmJpdHMgKGUuZy4sIEJhbGlzdGlkYWUpLg0KDQpgYGB7cixmaWcuYXNwPTEuNSxmaWcuY2FwPSIocmVmOmFzcGVjdHJhdGlvKSJ9DQpncmlkLmFycmFuZ2UoDQpnZ3Bsb3QoZGF0YV9maW5hbCAlPiUNCiAgICAgICAgIGRyb3BfbmEocHJlY2F1ZGFsX2xlbmd0aCxoZWFkX2xlbmd0aCxib2R5X2RlcHRoLHNoYXBlKSAlPiUNCiAgICAgICAgIGZpbHRlcihsZW5ndGhfYXM9PSJ0b3RhbCBsZW5ndGgiKSU+JQ0KICAgICAgICAgZ3JvdXBfYnkodGF4b24pJT4lIHN1bW1hcmlzZShhY3Jvc3MoYyhoZWFkX2xlbmd0aCxwcmVjYXVkYWxfbGVuZ3RoLGJvZHlfZGVwdGgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVhbiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNoYXBlPXVuaXF1ZShzaGFwZSksY2xhZGU9dW5pcXVlKGNsYWRlKSxnZW51cz11bmlxdWUoZ2VudXMpLGZhbWlseT11bmlxdWUoZmFtaWx5KSksDQogICAgICAgYWVzKHk9Ym9keV9kZXB0aC9wcmVjYXVkYWxfbGVuZ3RoLHg9aGVhZF9sZW5ndGgvcHJlY2F1ZGFsX2xlbmd0aCkpKw0KICBnZW9tX3N0YXIoYWVzKGZpbGw9c2hhcGUsc3RhcnNoYXBlPWNsYWRlKSkrDQogIGdlb21fc3RhcihkYXRhPS4lPiVmaWx0ZXIoZmFtaWx5ICVpbiUgYygiQmFsaXN0aWRhZSIsIk1vbmFjYW50aGlkYWUiLCJBY2FudGh1cmlkYWUiKSksDQogICAgICAgICAgICAgIGFlcyhzdGFyc2hhcGU9Y2xhZGUpLGZpbGw9IndoaXRlIikrDQogIGdlb21fc3RhcihkYXRhPS4lPiVmaWx0ZXIoZmFtaWx5PT0iU2VycmFuaWRhZSIpLA0KICAgICAgICAgICAgYWVzKHN0YXJzaGFwZT1jbGFkZSksZmlsbD0iYmxhY2siKSsNCiAgc2NhbGVfc3RhcnNoYXBlX21hbnVhbCh2YWx1ZXM9YygxNSwxMywxMiwxLDEyKSkrDQogIGxhYnMoeD0iUGVyY2VudCBIZWFkIExlbmd0aCIseT0iQXNwZWN0IFJhdGlvIG9mIEJvZHkiKSsNCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdD1tZWFuKGhlYWRfbGVuZ3RoL3ByZWNhdWRhbF9sZW5ndGgpKSxsaW5ldHlwZT0iZGFzaGVkIikrDQogIHRoZW1lX2NsYXNzaWMoKSsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSwNCmdncGxvdChkYXRhX2ZpbmFsICU+JQ0KICAgICAgICAgZHJvcF9uYShwcmVjYXVkYWxfbGVuZ3RoLE9PTCxib2R5X2RlcHRoLHNoYXBlKSAlPiUNCiAgICAgICAgIGZpbHRlcihsZW5ndGhfYXM9PSJ0b3RhbCBsZW5ndGgiKSU+JQ0KICAgICAgICAgZ3JvdXBfYnkodGF4b24pJT4lIHN1bW1hcmlzZShhY3Jvc3MoYyhPT0wscHJlY2F1ZGFsX2xlbmd0aCxib2R5X2RlcHRoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lYW4pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaGFwZT11bmlxdWUoc2hhcGUpLGNsYWRlPXVuaXF1ZShjbGFkZSksZ2VudXM9dW5pcXVlKGdlbnVzKSxmYW1pbHk9dW5pcXVlKGZhbWlseSkpLA0KICAgICAgIGFlcyh5PWJvZHlfZGVwdGgvcHJlY2F1ZGFsX2xlbmd0aCx4PU9PTC9wcmVjYXVkYWxfbGVuZ3RoKSkrDQogIGdlb21fc3RhcihhZXMoZmlsbD1zaGFwZSxzdGFyc2hhcGU9Y2xhZGUpKSsNCiAgZ2VvbV9zdGFyKGRhdGE9LiU+JWZpbHRlcihmYW1pbHkgJWluJSBjKCJCYWxpc3RpZGFlIiwiTW9uYWNhbnRoaWRhZSIsIkFjYW50aHVyaWRhZSIpKSwNCiAgICAgICAgICAgIGFlcyhzdGFyc2hhcGU9Y2xhZGUpLGZpbGw9IndoaXRlIikrDQogIGdlb21fc3RhcihkYXRhPS4lPiVmaWx0ZXIoZmFtaWx5PT0iU2VycmFuaWRhZSIpLA0KICAgICAgICAgICAgYWVzKHN0YXJzaGFwZT1jbGFkZSksZmlsbD0iYmxhY2siKSsNCiAgc2NhbGVfc3RhcnNoYXBlX21hbnVhbCh2YWx1ZXM9YygxNSwxMywyOCwxLDExKSkrDQogIGxhYnMoeD0iUGVyY2VudCBPcmJpdC1PcGVyY3VsYXIgTGVuZ3RoIix5PSJBc3BlY3QgUmF0aW8gb2YgQm9keSIsDQogICAgICAgc3RhcnNoYXBlPSJDbGFkZSIsZmlsbD0iU2hhcGUiKSsNCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdD1tZWFuKE9PTC9wcmVjYXVkYWxfbGVuZ3RoKSksbGluZXR5cGU9ImRhc2hlZCIpKw0KICB0aGVtZV9jbGFzc2ljKCkrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSsNCiAgZ3VpZGVzKGZpbGw9Z3VpZGVfbGVnZW5kKG5jb2w9MiksDQogICAgICAgICBzdGFyc2hhcGU9Z3VpZGVfbGVnZW5kKG5jb2w9MikpLA0KaGVpZ2h0cz1jKDIsMi4yKQ0KKQ0KYGBgDQoNCkFzIGNhbiBiZSBzZWVuIGJ5IHRoaXMgZ3JhcGgsIHRoZXJlIGlzIGEgc2xpZ2h0IGNvcnJlbGF0aW9uIGJldHdlZW4gcGVyY2VudCBoZWFkIGxlbmd0aCBhbmQgYXhpYWwgZWxvbmdhdGlvbiwgcGFydGljdWxhcmx5IGluIG1vcmUtZWxvbmdhdGUgYm9kaWVkIHRheGEsIGJ1dCB0aGUgY29ycmVsYXRpb24gaXMgbm90IHN0cm9uZyBhbmQgaXQgaXMgbm90IG9uZSB0aGF0IGNhbiBiZSBlYXNpbHkgYXBwcm94aW1hdGVkIGJ5IGEgbGluZWFyIG1vZGVsIChpLmUuLCBpdHMgZWZmZWN0IGlzIG11Y2ggbW9yZSBwcm9ub3VuY2VkIG9uIGVsb25nYXRlIHRheGEgdGhhbiBpdCBpcyBpbiBjb21wcmVzc2lmb3JtIG9uZXMpLiBJbiBnZW5lcmFsLCBoZWFkLWJvZHkgcHJvcG9ydGlvbnMgcmVtYWluIGZhaXJseSBjb25zaXN0ZW50IGFjcm9zcyBmaXNoZXMgb2YgZGl2ZXJzZSBib2R5IHNoYXBlcw0KDQojIyBFc3RpbmcgYWNjdXJhY3kgaW4gc3BlY2llcyB3aXRoIGEgcG9zdGVyaW9ybHkgc2hpZnRlZCBvcmJpdCB1c2luZyAqQmFsaXN0ZXMgdmlyZXNjZW5zKg0KDQpgYGB7cixjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30NCnJiaW5kKGRhdGFfZmluYWwlPiUNCiAgICAgICAgZmlsdGVyKHJlZmVyZW5jZXM9PSJBZnJpc2FsIGV0IGFsLiAyMDE5IiksDQogICAgICBkYXRhX2ZpbmFsJT4lDQogICAgICAgIGZpbHRlcihyZWZlcmVuY2VzPT0iQWZyaXNhbCBldCBhbC4gMjAxOSIpJT4lDQogICAgICAgIG11dGF0ZShPT0w9Ny4xKSklPiUNCiAgZGF0YS5mcmFtZSgpJT4lDQogIG11dGF0ZShmaXQ9ZXhwKHByZWRpY3QoZml0Lk9PTCwuKSkqcmVncmVzc2lvbi5zdGF0cyhmaXQuT09MKSRDRiwNCiAgICAgICAgIFBFPShmaXQtdG90YWxfbGVuZ3RoKS9maXQsDQogICAgICAgICBhbnRlcmlvcl9sYW5kbWFyaz1jKCJBbnRlcmlvciBtYXJnaW4gb2Ygb3JiaXQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQW50ZXJpb3IgbWFyZ2luIG9mIGdpbGwgY2hhbWJlciIpLA0KICAgICAgICAgdGF4b249c3RyX3JlcGxhY2UodGF4b24sIl8iLCIgIikpJT4lDQogIHNlbGVjdCh0YXhvbixhbnRlcmlvcl9sYW5kbWFyayxPT0wsdG90YWxfbGVuZ3RoLGZpdCxQRSklPiUNCiAga2FibGUoZGlnaXRzPWMoMSwxLDEsMSwxLDMpLA0KICAgICAgICBhbGlnbj1jKCJsIiwiYyIsImMiLCJjIiwiYyIsImMiKSwNCiAgICAgICAgY29sLm5hbWVzPWMoIlRheG9uIiwiQW50ZXJpb3IgTGFuZG1hcmsgZm9yIE9PTCIsIk9PTCIsIlRvdGFsIExlbmd0aCIsIkVzdGltYXRlZCBMZW5ndGgiLCJQRSIpLA0KICAgICAgICBjYXB0aW9uPSJDb21wYXJpc29uIG9mIGVycm9yIHJhdGVzIGZvciBPT0wgaW4gc3BlY2llcyB3aXRoIGEgcG9zdGVyaW9ybHkgc2hpZnRlZCBvcmJpdCB1c2luZyBhIHJlcHJlc2VudGF0aXZlIHNrZWxldG9uaXplZCBzcGVjaW1lbiBvZiA8aT5CYWxpc3RlcyB2aXJlc2NlbnM8L2k+IGZyb20gQWZyaXNhbCBldCBhbC4gKDIwMTkpIiklPiUNCiAgY29sdW1uX3NwZWMoMSwgaXRhbGljID0gVCklPiUNCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KQmFzZWQgb24gdGhpcywgaXQgbG9va3MgbGlrZSB0aGUgcmVhc29uIGZvciB0aGUgaGlnaCBlcnJvcnMgaW4gdGF4YSBzdWNoIGFzIEJhbGlzdG9pZGVpIGFuZCBtYW55IEFjYW50aHVyaWZvcm1lcyBoYXMgbm90aGluZyB0byBkbyB3aXRoIHRoZSB2ZW50cmFsbHkgc2hpZnRlZCBnaWxsIG9wZW5pbmcuIEluc3RlYWQsIGl0IGFwcGVhcnMgdG8gYmUgZHVlIHRvIGEgcG9zdGVyaW9yIHNoaWZ0IGluIHRoZSBvcmJpdCByZWxhdGl2ZSB0byB0aGUgb3ZlcmFsbCBsZW5ndGggb2YgdGhlIHNrdWxsIGluIHRoZXNlIHRheGEuDQoNClRodXMsIHRoaXMgbWVhbnMgdGhhdCBpbiBhcnRocm9kaXJlcyAoaW4gd2hpY2ggdGhlIGdpbGxzIGFyZSBtb3JlIHZlbnRyYWwgdG8gdGhlIGhlYWQgdGhhbiBwb3N0ZXJpb3IgbGlrZSBpbiBvdGhlciBmaXNoZXMpIHRoaXMgc2hpZnQgaW4gdGhlIGJyYW5jaGlhbCBza2VsZXRvbiBzaG91bGQgbm90IHJlc3VsdCBpbiBzeXN0ZW1hdGljIGVycm9ycyBpbiBPT0wuIEluZGVlZCwgaXQgc29tZXdoYXQgc3VwcG9ydHMgdGhlIHNob3J0ZXIgbGVuZ3RocyBmb3IgKkR1bmtsZW9zdGV1cyB0ZXJyZWxsaSogcmVwb3J0ZWQgaGVyZSwgYXMgc3VjaCBhIHNoaWZ0IHR5cGljYWxseSBvbmx5IG9jY3VycyBpbiBmaXNoZXMgd2l0aCBzaG9ydGVyIGJvZGllcy4NCg0KIyBDb21wYXJpbmcgdG90YWwgbGVuZ3RocyBpbiBwbGFjb2Rlcm1zIHdpdGgga25vd24gdG90YWwgbGVuZ3RoDQoNCiMjIFByZWRpY3RpbmcgdG90YWwgbGVuZ3RoIGluIGNvbXBsZXRlIEFydGhyb2RpcmENCg0KYGBge3IsY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZSd9DQpjb21wbGV0ZV9hcnRocm9kaXJlX2xlbmd0aF9lc3RpbWF0ZXM8LWZvc3NpbF90YXhhJT4lDQogIGZpbHRlcighIWZvc3NpbC5zcGVjaW1lbnMsZ2VudXMhPSJEdW5rbGVvc3RldXMiKSU+JQ0KICBhdWdtZW50KGZpdC5PT0wsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0aW9uIiklPiUNCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0Lk9PTCkkQ0YpKSU+JQ0KICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnLicpKSwgZnVucyhwYXN0ZTAoImZpdDEiLC4pKSklPiUNCiAgYXVnbWVudChmaXQuc3BlY2llc19hdmVyYWdlLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdGlvbiIpJT4lDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5zcGVjaWVzX2F2ZXJhZ2UpJENGKSklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCJmaXQyIiwuKSkpJT4lDQogIG11dGF0ZShhY3Jvc3MoLmNvbHM9Yyh0b3RhbF9sZW5ndGgsDQogICAgICAgICAgICAgICAgICAgICAgICBmaXQxLmZpdHRlZCxmaXQxLmxvd2VyLGZpdDEudXBwZXIsDQogICAgICAgICAgICAgICAgICAgICAgICBmaXQyLmZpdHRlZCxmaXQyLmxvd2VyLGZpdDIudXBwZXIpLHJvdW5kLDIpLA0KICAgICAgICAgZml0MS5yYW5nZT1wYXN0ZTAoIigiLHNwcmludGYoIiUuMWYiLHJvdW5kKGZpdDEubG93ZXIsMSkpLCLigJMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgc3ByaW50ZigiJS4xZiIscm91bmQoZml0MS51cHBlciwxKSksIikiKSwNCiAgICAgICAgIGZpdDIucmFuZ2U9cGFzdGUwKCIoIixmaXQyLmxvd2VyLCLigJMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgc3ByaW50ZigiJS4xZiIscm91bmQoZml0Mi51cHBlciwxKSksIikiKSwNCiAgICAgICAgIGZpdDEuUEU9cGFzdGUwKCIoIixzcHJpbnRmKCIlLjFmIixyb3VuZChmaXQxLmZpdHRlZCooMS0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKGZpdC5PT0wpJGFkalBFLzEwMCksMSkpLA0KICAgICAgICAgICAgICAgICAgICAi4oCTIixzcHJpbnRmKCIlLjFmIixyb3VuZChmaXQxLmZpdHRlZCooMSsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKGZpdC5PT0wpJGFkalBFLzEwMCksMSkpLCIpIiksDQogICAgICAgICBmaXQyLlBFPXBhc3RlMCgiKCIsc3ByaW50ZigiJS4xZiIscm91bmQoZml0Mi5maXR0ZWQqKDEtDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQuc3BlY2llc19hdmVyYWdlKSRhZGpQRS8xMDApLDEpKSwNCiAgICAgICAgICAgICAgICAgICAgIuKAkyIsc3ByaW50ZigiJS4xZiIscm91bmQoZml0Mi5maXR0ZWQqKDErDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQuc3BlY2llc19hdmVyYWdlKSRhZGpQRS8xMDApLDEpKSwiKSIpLA0KICAgICAgICAgUEU9KChmaXQxLmZpdHRlZC10b3RhbF9sZW5ndGgpL2ZpdDEuZml0dGVkKSU+JXJvdW5kKDQpLA0KICAgICAgICAgUEUyPSgoZml0Mi5maXR0ZWQtdG90YWxfbGVuZ3RoKS9maXQyLmZpdHRlZCklPiVyb3VuZCg0KSkNCmNvbXBsZXRlX2FydGhyb2RpcmVfbGVuZ3RoX2VzdGltYXRlcyU+JQ0KICByb3duYW1lc190b19jb2x1bW4oKSU+JQ0KICBzZWxlY3QodGF4b24sc3BlY2ltZW4sdG90YWxfbGVuZ3RoLA0KICAgICAgICAgZml0MS5maXR0ZWQsZml0MS5QRSxmaXQxLnJhbmdlLFBFLA0KICAgICAgICAgZml0Mi5maXR0ZWQsZml0Mi5QRSxmaXQyLnJhbmdlLFBFMiklPiUNCiAga2FibGUoZGlnaXRzPWMoMSwxLDEsMSwxLDEsMywxLDEsMSwzKSwNCiAgICAgICAgY29sLm5hbWVzPWMoIlRheG9uIiwiU3BlY2ltZW4iLCJBY3R1YWwiLCJFc3QuIiwiKy8tIFBFIiwiOTUlIFAuSS4iLCIlUEUiLA0KICAgICAgICAgICAgICAgICAgICAiRXN0LiIsIisvLSBQRSIsIjk1JSBDLkkuIiwiJVBFIiksDQogICAgICAgIGFsaWduPWMoImwiLCJjIiwiYyIsImMiLCJjIiwiYyIsImMiLCJjIiwiYyIsImMiLCJjIiksDQogICAgICAgIGNhcHRpb249IkVzdGltYXRlZCBsZW5ndGhzIGluIHNwZWNpbWVucyBvZiBhcnRocm9kaXJlcyBrbm93biBmcm9tIHdob2xlLWJvZHkgcmVtYWlucyBhbmQgaW4gd2hpY2ggdG90YWwgbGVuZ3RoIGlzIGVpdGhlciBtZWFzdXJhYmxlIG9yIGNhbiBiZSBhcHByb3hpbWF0ZWQuIiklPiUNCiAgY29sdW1uX3NwZWMoMSwgaXRhbGljID0gVCklPiUNCiAgY29sdW1uX3NwZWMoYyg0LDgpLCBib2xkID0gVCklPiUNCiAgYWRkX2hlYWRlcl9hYm92ZShjKCIgIj0zLCJJbmRpdmlkdWFsIERhdGEiPTQsIlNwZWNpZXMgQXZlcmFnZXMiPTQpKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCg0KY29tcGxldGVfYXJ0aHJvZGlyZV9sZW5ndGhfZXN0aW1hdGVzJT4lDQogIGZpbHRlcihzcGVjaW1lbiAlaW4lIGMoIkNvbXBvc2l0ZSBNaWxsZXJvc3RldXMiLCJHZXNzIGFuZCBUcmluYWpzdGljIDIwMTciLA0KICAgICAgICAgICAgICAgICAgICAgICAgICJSZWNvbi4gKFRyaW5hanN0aWMgMjAxMykiLCJOTVMgMTg5My4xMDcuMjciLA0KICAgICAgICAgICAgICAgICAgICAgICAgICJGTU5IIFBGIDEwODkiLCJST00gVlAgNTI2NjQiLCJGTU5IIFBGIDE2NzMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICJSZWNvbi4gKE0gJiBXIDE5NjgpIiwiTk1TIDE5ODcuNy4xMTgiLCJSZWNvbi4gKE1pbGVzIDE5NzEpIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAiTU5ITSAyLTE3NyIsIk5NUyBHLjE5OTUuNC4yIiwiQUEuTUVNLkRTLjgiKSklPiUNCiAgc2VsZWN0KHRheG9uLHNwZWNpbWVuLHRvdGFsX2xlbmd0aCxmaXQxLmZpdHRlZCxmaXQxLlBFLGZpdDEucmFuZ2UsUEUsDQogICAgICAgICBmaXQyLmZpdHRlZCxmaXQyLlBFLGZpdDIucmFuZ2UsUEUyKSU+JQ0KICByZW5hbWUoVGF4b249dGF4b24sDQogICAgICAgICBTcGVjaW1lbj1zcGVjaW1lbiwNCiAgICAgICAgICJBY3R1YWwgTGVuZ3RoIj10b3RhbF9sZW5ndGgsDQogICAgICAgICAiRXN0aW1hdGVkIExlbmd0aCI9Zml0MS5maXR0ZWQsDQogICAgICAgICAiKy8tIFBFIj1maXQxLlBFLA0KICAgICAgICAgIjk1JSBQLkkuIj1maXQxLnJhbmdlLA0KICAgICAgICAgRXJyb3I9UEUsDQogICAgICAgICAiRXN0aW1hdGVkIExlbmd0aCAoU3BlY2llcyBBdmVyYWdlcykiPWZpdDIuZml0dGVkLA0KICAgICAgICAgIisvLSBQRSAgKFNwZWNpZXMgQXZlcmFnZXMpIj1maXQyLlBFLA0KICAgICAgICAgIjk1JSBQLkkuICAoU3BlY2llcyBBdmVyYWdlcykiPWZpdDIucmFuZ2UsDQogICAgICAgICAiRXJyb3IgKFNwZWNpZXMgQXZlcmFnZXMpIj1QRTIpJT4lDQogIHdyaXRlLnhsc3goZmlsZT0iRGV2b25pYW4gRmlzaCBUYWxlIFRhYmxlIDIgKENvbXBsZXRlIEFydGhyb2RpcmUgRXN0aW1hdGVzKS54bHN4IikNCmBgYA0KDQoqKk5vdGU6KiogSSBhbSBhIGxpdHRsZSBza2VwdGljYWwgZm9yIHRoZSByZXN1bHRzIG9mIE5ITVVLIFBWIFAgNDk2NjMuIFRoaXMgc3BlY2ltZW4gaXMgdmVyeSBtYW5nbGVkIGFuZCB0aGUgc2t1bGwgYW5kIHRob3JhY2ljIGFybW9yIGFyZSBhIG1hbmdsZWQgbWVzcyBvZiBwbGF0ZXMuIEl0IGlzIHZlcnkgaGFyZCB0byBzZWUgd2hlcmUgdGhlIGhlYWQgZW5kcyBhbmQgdGhlIGJvZHkgYmVnaW5zLCBhcyB3ZWxsIGFzIHRoZSBtYXJnaW5zIG9mIHRoZSBvcmJpdC4gSGVuY2UsIHRoZSBlc3RpbWF0ZWQgbGVuZ3RoIG1heSBiZSBpbiBlcnJvciBiZWNhdXNlIGl0IHdhcyBoYXJkIHRvIGZpZ3VyZSBvdXQgd2hlcmUgT09MIHdhcy4NCg0KYGBge3J9DQpyYmluZCgiTWVhbiBQZXJjZW50IEVycm9yIGZvciBFc3RpbWF0ZXMgVXNpbmcgSW5kaXZpZHVhbCBEYXRhIFBvaW50cyIgPSBjb21wbGV0ZV9hcnRocm9kaXJlX2xlbmd0aF9lc3RpbWF0ZXMlPiVwdWxsKFBFKSU+JWFicygpJT4lbWVhbigpLA0KIk1lYW4gUGVyY2VudCBFcnJvciBmb3IgU3BlY2llcyBBdmVyYWdlIERhdGEiID0gY29tcGxldGVfYXJ0aHJvZGlyZV9sZW5ndGhfZXN0aW1hdGVzJT4lcHVsbChQRTIpJT4lYWJzKCklPiVtZWFuKCkpJT4lDQogIGthYmxlKGRpZ2l0cz00LGNvbC5uYW1lcz0iUEUiLGFsaWduPSJjIiwNCiAgICAgICAgY2FwdGlvbj0iTWVhbiBlcnJvciBmb3IgYXJ0aHJvZGlyZXMgaW4gd2hpY2ggdG90YWwgbGVuZ3RoIGlzIGtub3duIGVzdGltYXRlZCB2aWEgT09MIiwNCiAgICAgICAgdGFibGUuYXR0ciA9ICJzdHlsZT0nd2lkdGg6NzAlOyciKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQpGcm9tIHRoaXMgaXQgY2FuIGJlIHNlZW4gdGhhdCwgd2l0aG91dCBhbnkgb3RoZXIgYWRkaXRpb25hbCBxdWFsaWZpZXJzLCBPT0wgZXN0aW1hdGVzIGJvZHkgbGVuZ3RoIGluIGFydGhyb2RpcmVzIHdpdGggYW4gYWNjdXJhY3kgb2YgYWJvdXQgKy8tIDEzLjUlDQoNCiMjIEFsbG9tZXRyeSBvZiBPT0wgaW4gQXJ0aHJvZGlyYQ0KDQojIyMgQWxsb21ldHJ5IG9mIE9PTCBpbiBDb2Njb3N0ZW9tb3JwaGENCg0KYGBge3J9DQpmb3NzaWxfdGF4YSU+JQ0KICBmaWx0ZXIoY2xhZGU9PSJQbGFjb2Rlcm1pIiwNCiAgICAgICAgIGxlbmd0aF9hcz09InRvdGFsIGxlbmd0aCIsDQogICAgICAgICAhZ2VudXMgJWluJSBjKCJIb2xvbmVtYSIsIkFtYXppY2h0aHlzIiwiQWZyaWNhbmFzcGlzIiwiTmV3c3BlY2llcyIpKSUkJQ0KICBsbShsb2coT09MKX5sb2codG90YWxfbGVuZ3RoKSwuKSU+JQ0KICBzdW1tYXJ5KCkNCmBgYA0KDQojIyMgQWxsb21ldHJ5IG9mIE9PTCBpbiBhbGwgY29tcGxldGUgQXJ0aHJvZGlyYQ0KDQpgYGB7cn0NCmZvc3NpbF90YXhhJT4lDQogIGZpbHRlcihjbGFkZT09IlBsYWNvZGVybWkiLA0KICAgICAgICAgbGVuZ3RoX2FzPT0idG90YWwgbGVuZ3RoIiklJCUNCiAgbG0obG9nKE9PTCl+bG9nKHRvdGFsX2xlbmd0aCksLiklPiUNCiAgc3VtbWFyeSgpDQpgYGANCg0KIyBQcmVkaWN0aW5nIGJvZHkgbGVuZ3RoIGluICpEdW5rbGVvc3RldXMgdGVycmVsbGkqDQoNCmBgYHtyfQ0KZm9zc2lsX3RheGElPiUNCiAgZmlsdGVyKCEhZm9zc2lsLnNwZWNpbWVucyxnZW51cz09IkR1bmtsZW9zdGV1cyIpJT4lDQogIGF1Z21lbnQoZml0Lk9PTCxuZXdkYXRhPS4saW50ZXJ2YWw9InByZWRpY3Rpb24iKSU+JQ0KICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnLicpKSwgZnVucyhwYXN0ZTAoImZpdDEiLC4pKSklPiUNCiAgbXV0YXRlKGFjcm9zcyhmaXQxLmZpdHRlZDpmaXQxLnVwcGVyLH5leHAoLikqcmVncmVzc2lvbi5zdGF0cyhmaXQuT09MKSRDRiksDQogICAgICAgICBmaXQxLlBFLmxvd2VyPWZpdDEuZml0dGVkKg0KICAgICAgICAgICAoKDEwMC1yZWdyZXNzaW9uLnN0YXRzKGZpdC5PT0wpJGFkalBFKS8xMDApLA0KICAgICAgICAgZml0MS5QRS51cHBlcj1maXQxLmZpdHRlZCoNCiAgICAgICAgICAgKCgxMDArcmVncmVzc2lvbi5zdGF0cyhmaXQuT09MKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIGZpdDEuUEU9cGFzdGUwKCIoIixyb3VuZChmaXQxLlBFLmxvd2VyLDEpLCLigJMiLHJvdW5kKGZpdDEuUEUudXBwZXIsMSksIikiKSwNCiAgICAgICAgIGZpdDEucmFuZ2U9cGFzdGUwKCIoIixyb3VuZChmaXQxLmxvd2VyLDEpLCLigJMiLHJvdW5kKGZpdDEudXBwZXIsMSksIikiKSklPiUNCiAgYXVnbWVudChmaXQuc3BlY2llc19hdmVyYWdlLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdGlvbiIpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgiZml0MiIsLikpKSU+JQ0KICBtdXRhdGUoYWNyb3NzKGZpdDIuZml0dGVkOmZpdDIudXBwZXIsfmV4cCguKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5zcGVjaWVzX2F2ZXJhZ2UpJENGKSwNCiAgICAgICAgIGZpdDIuUEUubG93ZXI9Zml0Mi5maXR0ZWQqKCgxMDAtcmVncmVzc2lvbi5zdGF0cyhmaXQuc3BlY2llc19hdmVyYWdlKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIGZpdDIuUEUudXBwZXI9Zml0Mi5maXR0ZWQqKCgxMDArcmVncmVzc2lvbi5zdGF0cyhmaXQuc3BlY2llc19hdmVyYWdlKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIGZpdDIuUEU9cGFzdGUwKCIoIixyb3VuZChmaXQyLlBFLmxvd2VyLDEpLCLigJMiLHJvdW5kKGZpdDIuUEUudXBwZXIsMSksIikiKSwNCiAgICAgICAgIGZpdDIucmFuZ2U9cGFzdGUwKCIoIixyb3VuZChmaXQyLmxvd2VyLDEpLCLigJMiLHJvdW5kKGZpdDIudXBwZXIsMSksIikiKSklPiUNCiAgc2VsZWN0KHNwZWNpbWVuLGZpdDEuZml0dGVkLGZpdDEuUEUsZml0MS5yYW5nZSxmaXQyLmZpdHRlZCxmaXQyLlBFLGZpdDIucmFuZ2UpJT4lDQogIGthYmxlKGRpZ2l0cz0xLA0KICAgICAgICBhbGlnbj1jKCJsIiwiYyIsImMiLCJjIiwiYyIsImMiLCJjIiksDQogICAgICAgIGNhcHRpb249ICJMZW5ndGggZXN0aW1hdGVzIGZvciBzcGVjaW1lbnMgb2YgPGk+RHVua2xlb3N0ZXVzIHRlcnJlbGxpPC9pPiB1c2luZyBhbiBhbGwtc3BlY2llcyBtb2RlbCwgd2l0aG91dCBhbnkgYWRkaXRpb25hbCBtb2RpZmljYXRpb25zIG9yIGV4Y2x1ZGluZyBzcGVjaW1lbnMiLA0KICAgICAgICBjb2wubmFtZXMgPSBjKCJTcGVjaW1lbiIsIkVzdC4iLCIrLy0gJVBFIiwiOTUlIFAuSS4iLA0KICAgICAgICAgICAgICAgICAgICAgICJFc3QuIiwiKy8tICVQRSIsIjk1JSBQLkkuIikpJT4lDQogIGFkZF9oZWFkZXJfYWJvdmUoYygiICI9MSwiSW5kaXZpZHVhbCBTcGVjaW1lbnMiPTMsIlNwZWNpZXMgQXZlcmFnZXMiPTMpKSU+JQ0KICBjb2x1bW5fc3BlYyhjKDIsNSksIGJvbGQgPSBUKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQojIyBQcmVkaWN0aW5nIHRvdGFsIGxlbmd0aCBleGNsdWRpbmcgdGF4YSB3aXRoIGV4dHJlbWUgYm9keSBzaGFwZXMNCg0KKipOb3RlOioqICJleHRyZW1lIGJvZHkgc2hhcGVzIiBhcyBkZWZpbmVkIGhlcmUsIGluY2x1ZGVzIGFuZ3VpbGlmb3JtLCBjb21wcmVzc2lmb3JtLCBhbmQgbWFjcnVyaWZvcm0gdGF4YSwgYXMgd2VsbCBhcyB0YXhhIHdpdGggcG9zdGVyaW9ybHkgc2hpZnRlZCBvcmJpdHMgbGlrZSBCYWxpc3RvaWRlaS4NCg0KSXQgc2VlbXMgaGlnaGx5IHVubGlrZWx5IHRoYXQgKkR1bmtsZW9zdGV1cyogaGFkIGFuIGFuZ3VpbGxpZm9ybSwgbWFjcnVyaWZvcm0sIG9yIHRydWx5IGNvbXByZXNzaWZvcm0gYm9keSBwbGFuICh0aG91Z2ggYSBzZW1pLWNvbXByZXNzaWZvcm0gbGFtbmlkLWxpa2UgYm9keSBwbGFuIGlzIHBvc3NpYmxlKS4gU2ltaWxhcmx5LCAqRHVua2xlb3N0ZXVzKiBjbGVhcmx5IGRvZXMgbm90IGRlbW9uc3RyYXRlIHRoZSBjcmFuaWFsIGZlYXR1cmVzIHRoYXQgY2F1c2Ugc29tZSBvdGhlciB0YXhhIHRvIGV4aGliaXQgaGlnaCBlcnJvciByYXRlcywgc3VjaCBhcyB0aGUgcG9zdGVyaW9ybHkgc2hpZnRlZCBvcmJpdHMgb2YgQmFsaXN0b2lkZWkgb3IgdGhlIGVsb25nYXRlIHJvc3RydW0gb2YgKlJoaW5vY2hpbWFlcmEqIG9yICpNaXRzdWt1cmluYSouIFJ1bGluZyBvdXQgdGhlIGVubGFyZ2VkIGhlYWRzIG9mIGdyb3VwZXJzIGlzIG1vcmUgZGlmZmljdWx0IChhcyBvbmUgbWlnaHQgZXhwZWN0ICpEdW5rbGVvc3RldXMqIHRvIGhhdmUgYSBsYXJnZSBoZWFkKSwgYnV0IGl0IHNlZW1zIGxpa2UgYW4gdW5saWtlbHkgaW5mZXJlbmNlIGdpdmVuIHRoYXQgb3RoZXIgYXJ0aHJvZGlyZXMgc2hvdyBoZWFkLWJvZHkgcHJvcG9ydGlvbnMgd2l0aGluIHRoZSByYW5nZSBvZiB2YXJpYXRpb24gb2YgInR5cGljYWwiIChpLmUuLCBub24tZ3JvdXBlcikgZmlzaGVzLiBUaHVzLCBhbGwgb2YgdGhlc2UgdGF4YSBjYW4gYmUgc2FmZWx5IGV4Y2x1ZGVkIGZyb20gdGhlIHJlZ3Jlc3Npb24gbW9kZWwuIEhvd2V2ZXIsIGl0IGlzIG5vdCBwb3NzaWJsZSB0byBmdWxseSBydWxlIG91dCBhbiBlbG9uZ2F0ZSBib2R5IHBsYW4gYXMgc2VlbiBpbiAqQ29yeXBoYWVuYSogb3Igc29tZSBJc3Rpb3Bob3JpZm9ybWVzLCB0aG91Z2ggdGhlcmUgaXMgbm8gZXZpZGVuY2UgZnJvbSB0aGUgcHJlc2VydmVkIHJlbWFpbnMgb2YgKkR1bmtsZW9zdGV1cyogdG8gc3VnZ2VzdCB0aGF0IHRoaXMgdGF4b24gaGFkIHRoaXMgdHlwZSBvZiBib2R5IHBsYW4uDQoNClRodXMsIGEgbW9kZWwgd2FzIGZpdCBjb21wcmlzZWQgc29sZWx5IG9mIGZ1c2lmb3JtIGFuZCBlbG9uZ2F0ZS1ib2RpZWQgdGF4YSwgZXhjbHVkaW5nIHRob3NlIG90aGVyIHRheGEgd2hpY2ggZXhoaWJpdGVkIGRpc2Nlcm5hYmxlIHNwZWNpYWxpemF0aW9ucyB0aGF0IGNvdWxkIGJlIHJ1bGVkIG91dCBvciB3ZXJlIGhpZ2hseSB1bmxpa2VseSBpbiAqRHVua2xlb3N0ZXVzKi4NCg0KYGBge3J9DQpmaXQubm9fZXh0cmVtZV9zaGFwZXM8LWxtKGxvZyh0b3RhbF9sZW5ndGgpfmxvZyhPT0wpLA0KICAgICAgICAgICAgICAgICAgZGF0YV9maW5hbCU+JWZpbHRlcighc2hhcGUgJWluJQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYygiYW5ndWlsaWZvcm0iLCJjb21wcmVzc2lmb3JtIiwibWFjcnVyaWZvcm0iKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAhZmFtaWx5ICVpbiUgYygiQmFsaXN0aWRhZSIsICJNb25hY2FudGhpZGFlIiwgIlNlcnJhbmlkYWUiLCJIb2xvY2VudHJpZGFlIikpKQ0KDQpzdW1tYXJ5KGZpdC5ub19leHRyZW1lX3NoYXBlcykNCg0KcmVncmVzc2lvbi5zdGF0cyhmaXQubm9fZXh0cmVtZV9zaGFwZXMpDQoNCmZvc3NpbF90YXhhJT4lDQogIGZpbHRlcighIWZvc3NpbC5zcGVjaW1lbnMsZ2VudXM9PSJEdW5rbGVvc3RldXMiKSU+JQ0KICBhdWdtZW50KGZpdC5ub19leHRyZW1lX3NoYXBlcyxuZXdkYXRhPS4saW50ZXJ2YWw9InByZWRpY3Rpb24iKSU+JQ0KICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnLicpKSwgZnVucyhwYXN0ZTAoImZpdDEiLC4pKSklPiUNCiAgbXV0YXRlKGFjcm9zcyhmaXQxLmZpdHRlZDpmaXQxLnVwcGVyLH5leHAoLikqcmVncmVzc2lvbi5zdGF0cyhmaXQubm9fZXh0cmVtZV9zaGFwZXMpJENGKSwNCiAgICAgICAgIGZpdDEuUEUubG93ZXI9Zml0MS5maXR0ZWQqKCgxMDAtcmVncmVzc2lvbi5zdGF0cyhmaXQubm9fZXh0cmVtZV9zaGFwZXMpJGFkalBFKS8xMDApLA0KICAgICAgICAgZml0MS5QRS51cHBlcj1maXQxLmZpdHRlZCooKDEwMCtyZWdyZXNzaW9uLnN0YXRzKGZpdC5ub19leHRyZW1lX3NoYXBlcykkYWRqUEUpLzEwMCksDQogICAgICAgICBmaXQxLlBFPXBhc3RlMCgiKCIscm91bmQoZml0MS5QRS5sb3dlciwxKSwi4oCTIixyb3VuZChmaXQxLlBFLnVwcGVyLDEpLCIpIiksDQogICAgICAgICBmaXQxLnJhbmdlPXBhc3RlMCgiKCIscm91bmQoZml0MS5sb3dlciwxKSwi4oCTIixyb3VuZChmaXQxLnVwcGVyLDEpLCIpIikpJT4lDQogIGF1Z21lbnQoZml0LnNwZWNpZXNfYXZlcmFnZTIsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0aW9uIiklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCJmaXQyIiwuKSkpJT4lDQogIG11dGF0ZShhY3Jvc3MoZml0Mi5maXR0ZWQ6Zml0Mi51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0Lm5vX2V4dHJlbWVfc2hhcGVzKSRDRiksDQogICAgICAgICBmaXQyLlBFLmxvd2VyPWZpdDIuZml0dGVkKigoMTAwLXJlZ3Jlc3Npb24uc3RhdHMoZml0Lm5vX2V4dHJlbWVfc2hhcGVzKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIGZpdDIuUEUudXBwZXI9Zml0Mi5maXR0ZWQqKCgxMDArcmVncmVzc2lvbi5zdGF0cyhmaXQubm9fZXh0cmVtZV9zaGFwZXMpJGFkalBFKS8xMDApLA0KICAgICAgICAgZml0Mi5QRT1wYXN0ZTAoIigiLHJvdW5kKGZpdDIuUEUubG93ZXIsMSksIuKAkyIscm91bmQoZml0Mi5QRS51cHBlciwxKSwiKSIpLA0KICAgICAgICAgZml0Mi5yYW5nZT1wYXN0ZTAoIigiLHJvdW5kKGZpdDIubG93ZXIsMSksIuKAkyIscm91bmQoZml0Mi51cHBlciwxKSwiKSIpKSU+JQ0KICBzZWxlY3Qoc3BlY2ltZW4sZml0MS5maXR0ZWQsZml0MS5QRSxmaXQxLnJhbmdlLGZpdDIuZml0dGVkLGZpdDIuUEUsZml0Mi5yYW5nZSklPiUNCiAga2FibGUoZGlnaXRzPTEsDQogICAgICAgIGFsaWduPWMoImwiLCJjIiwiYyIsImMiLCJjIiwiYyIsImMiKSwNCiAgICAgICAgY2FwdGlvbj0gIkxlbmd0aCBlc3RpbWF0ZXMgZm9yIHNwZWNpbWVucyBvZiA8aT5EdW5rbGVvc3RldXMgdGVycmVsbGk8L2k+IHVzaW5nIGEgbW9kZWwgZXhjbHVkaW5nIGFuZ3VpbGxpZm9ybSwgc2hvcnQtYm9kaWVkLCBhbmQgbWFjcnVyaWZvcm0gdGF4YSIsDQogICAgICAgIGNvbC5uYW1lcyA9IGMoIlNwZWNpbWVuIiwiRXN0LiIsIisvLSAlUEUiLCI5NSUgUC5JLiIsDQogICAgICAgICAgICAgICAgICAgICAgIkVzdC4iLCIrLy0gJVBFIiwiOTUlIFAuSS4iKSklPiUNCiAgYWRkX2hlYWRlcl9hYm92ZShjKCIgIj0xLCJJbmRpdmlkdWFsIFNwZWNpbWVucyI9MywiU3BlY2llcyBBdmVyYWdlcyI9MykpJT4lDQogIGNvbHVtbl9zcGVjKGMoMiw1KSwgYm9sZCA9IFQpJT4lDQogIGthYmxlX3N0eWxpbmcoKQ0KYGBgDQoNCiMjIFBsb3Qgb2YgZXN0aW1hdGVkIGxlbmd0aHMgZm9yICpEdW5rbGVvc3RldXMqIHVzaW5nIG9yYml0LW9wZXJjdWxhciBsZW5ndGgNCg0KYGBge3J9DQooZHVua2xlb3N0ZXVzX2xlbmd0aDwtZ2dwbG90KGRhdGFfZmluYWwlPiUNCiAgICAgICAgIGNiaW5kKGV4cChwcmVkaWN0KGxtKGxvZyh0b3RhbF9sZW5ndGgpfmxvZyhPT0wpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YV9maW5hbCU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoIXNoYXBlICVpbiUgYygiY29tcHJlc3NpZm9ybSIsImFuZ3VpbGlmb3JtIiwibWFjcnVyaWZvcm0iKSklPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKGNsYWRlIT0iUGxhY29kZXJtaSIpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfZmluYWwsaW50ZXJ2YWw9InByZWRpY3Rpb24iKSkpJT4lDQogICAgICAgICBmaWx0ZXIoIWNsYWRlICVpbiUgYygiUGV0cm9teXpvbnRpZm9ybWVzIikpLA0KICAgICAgIGFlcyh5PXRvdGFsX2xlbmd0aCx4PU9PTCkpKw0KICBnZW9tX3BvaW50KGFlcyhzaGFwZT1jbGFkZSksY29sb3I9ImRhcmsgZ3JleSIsZmlsbD0ibGlnaHQgZ3JleSIpKw0KICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIixhbHBoYT0wLjI1LHNlPUYpKw0KICBnZW9tX2xpbmUoYWVzKHk9bHdyKSwgY29sb3IgPSAiIzMzNjZGRiIsIGxpbmV0eXBlID0gImRhc2hlZCIsYWxwaGE9MC41KSsNCiAgZ2VvbV9saW5lKGFlcyh5PXVwciksIGNvbG9yID0gIiMzMzY2RkYiLCBsaW5ldHlwZSA9ICJkYXNoZWQiLGFscGhhPTAuNSkrDQogIGdlb21fZXJyb3JiYXIoZGF0YT0uJT4lZmlsdGVyKHNwZWNpbWVuICVpbiUgYygiQ01OSCA1NzY4IiwgIkNNTkggNzA1NCIsICJDTU5IIDYwOTAiLCAiQ01OSCA3NDI0IikpLA0KICAgICAgICAgICAgICAgIGFlcyh5bWluPWx3cix5bWF4PXVwciksd2lkdGg9MC4xKSsNCiAgZ2VvbV9wb2ludChkYXRhPS4lPiVmaWx0ZXIoc3BlY2ltZW4gJWluJSBjKCJDTU5IIDU3NjgiLCAiQ01OSCA3MDU0IiwgIkNNTkggNjA5MCIsICJDTU5IIDc0MjQiKSksDQogICAgICAgICAgICAgYWVzKHNoYXBlPWNsYWRlLHk9Zml0KSxmaWxsPSJibGFjayIsc2l6ZT0yKSsNCiAgc2NhbGVfc2hhcGVfbWFudWFsKHZhbHVlcz1jKDIxLDI0LDIyLDIzKSxndWlkZT0ibm9uZSIpKw0KICBzY2FsZV94X2NvbnRpbnVvdXModHJhbnM9J2xvZzEwJykrDQogIHNjYWxlX3lfY29udGludW91cyh0cmFucz0nbG9nMTAnKSsNCiAgbGFicyh4PSJPcmJpdC1PcGVyY3VsYXIgTGVuZ3RoIChjbSkiLHk9IlRvdGFsIExlbmd0aCAoY20pIikrDQogIHRoZW1lX2NsYXNzaWMoKSsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPWMoMC44LDAuMjUpKSkNCmBgYA0KDQojIyBEYXRhIHBsb3R0ZWQgb24gYSBkZXRyYW5zZm9ybWVkIHNjYWxlDQoNCihyZWY6ZGV0cmFuc2Zvcm1lZCkgTGVuZ3RoIGVzdGltYXRlcyBmb3IgKkR1bmtsZW9zdGV1cyB0ZXJyZWxsaSogcGxvdHRlZCBvbiBhIG5vbi1sb2cgdHJhbnNmb3JtZWQgc2NhbGUuDQoNCmBgYHtyLGZpZy5jYXA9IihyZWY6ZGV0cmFuc2Zvcm1lZCkifQ0KZ2dwbG90KGRhdGFfZmluYWwlPiUNCiAgICAgICAgIGRyb3BfbmEoT09MKSU+JQ0KICAgICAgICAgZmlsdGVyKGNsYWRlICVpbiUgYygiQWN0aW5vcHRlcnlnaWkiLCJTYXJjb3B0ZXJ5Z2lpIiwiQ2hvbmRyaWNodGh5ZXMiLCJQbGFjb2Rlcm1pIiwiUGV0cm9teXpvbnRpZm9ybWVzIikpJT4lDQogICAgICAgICBhdWdtZW50KGZpdC5PT0wsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0aW9uIiklPiUNCiAgICAgICAgIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5PT0wpJENGKSksDQogICAgICAgYWVzKHk9dG90YWxfbGVuZ3RoLHg9T09MKSkrDQogIGNvb3JkX2NhcnRlc2lhbih5PWMoMCw2MDApLHg9YygwLDkwKSkrDQogIGdlb21fcG9seWdvbihkYXRhPWRhdGEuZnJhbWUoT09MPWMoMCw3MS43NSw4NSksdG90YWxfbGVuZ3RoPWMoMCw3NTAsNzUwKSksDQogICAgICAgICAgICAgICBhbHBoYT0wLjUsZmlsbD0ieWVsbG93IikrDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDQ2MCxsaW5ldHlwZT0iZG90dGVkIixjb2xvcj0icmVkIikrDQogIGdlb21fc2VnbWVudChkYXRhPS4lPiVmaWx0ZXIoISFmb3NzaWwuc3BlY2ltZW5zLGdlbnVzPT0iRHVua2xlb3N0ZXVzIiksDQogICAgICAgICAgICAgICBhZXMoeT0ubG93ZXIseWVuZD0ubG93ZXIseD0tMjAseGVuZD1PT0wpLA0KICAgICAgICAgICAgICAgbGluZXR5cGU9ImRhc2hlZCIsYWxwaGE9MC43NSkrDQogIGdlb21fc2VnbWVudChkYXRhPS4lPiVmaWx0ZXIoISFmb3NzaWwuc3BlY2ltZW5zLGdlbnVzPT0iRHVua2xlb3N0ZXVzIiksDQogICAgICAgICAgICAgICBhZXMoeT0udXBwZXIseWVuZD0udXBwZXIseD0tMjAseGVuZD1PT0wpLA0KICAgICAgICAgICAgICAgbGluZXR5cGU9ImRhc2hlZCIsYWxwaGE9MC43NSkrDQogIHNjYWxlX3NoYXBlX21hbnVhbCh2YWx1ZXM9YygxNSwxMywxMSwxLDI4KSxndWlkZT0ibm9uZSIpKw0KICBnZW9tX3BvaW50KGFlcyhzdGFyc2hhcGU9Y2xhZGUpLGNvbG9yPSJkYXJrIGdyZXkiLGZpbGw9ImxpZ2h0IGdyZXkiKSsNCiAgZ2VvbV9saW5lKGFlcyh5PS5maXR0ZWQpLCBjb2xvciA9ICIjMzM2NkZGIixzaXplPTEpKw0KICBnZW9tX2xpbmUoYWVzKHk9Lmxvd2VyKSwgY29sb3IgPSAiIzMzNjZGRiIsIGxpbmV0eXBlID0gImRhc2hlZCIsYWxwaGE9MC41KSsNCiAgZ2VvbV9saW5lKGFlcyh5PS51cHBlciksIGNvbG9yID0gIiMzMzY2RkYiLCBsaW5ldHlwZSA9ICJkYXNoZWQiLGFscGhhPTAuNSkrDQogIGdlb21fZXJyb3JiYXIoZGF0YT0uJT4lZmlsdGVyKCEhZm9zc2lsLnNwZWNpbWVucyxnZW51cz09IkR1bmtsZW9zdGV1cyIpLA0KICAgICAgICAgICAgICAgIGFlcyh5bWluPS5sb3dlcix5bWF4PS51cHBlciksd2lkdGg9MykrDQogIGdlb21fcG9pbnQoZGF0YT0uJT4lZmlsdGVyKCEhZm9zc2lsLnNwZWNpbWVucyxnZW51cz09IkR1bmtsZW9zdGV1cyIpLA0KICAgICAgICAgICAgIGFlcyhzdGFyc2hhcGU9Y2xhZGUseT0uZml0dGVkKSxmaWxsPSJibGFjayIsc2l6ZT0yKSsNCiAgbGFicyh4PSJPcmJpdC1PcGVyY3VsYXIgTGVuZ3RoIChjbSkiLHk9IlRvdGFsIExlbmd0aCAoY20pIikrDQogIHRoZW1lX2NsYXNzaWMoKSsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPWMoMC44LDAuMjUpKSsNCiAgZ2VvbV9sYWJlbChkYXRhPS4lPiVmaWx0ZXIoISFmb3NzaWwuc3BlY2ltZW5zLGdlbnVzPT0iRHVua2xlb3N0ZXVzIiksDQogICAgICAgICAgICAgYWVzKHk9aWZlbHNlKHNwZWNpbWVuPT0iQ01OSCA2MDkwIiwudXBwZXItNCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHNwZWNpbWVuPT0iQ01OSCA3MDU0IiwudXBwZXIrMiwudXBwZXIpKSwNCiAgICAgICAgICAgICAgICAgeD0wLGxhYmVsPXJvdW5kKC51cHBlciwxKSksc2l6ZT0zKSsNCiAgZ2VvbV9sYWJlbChkYXRhPS4lPiVmaWx0ZXIoISFmb3NzaWwuc3BlY2ltZW5zLGdlbnVzPT0iRHVua2xlb3N0ZXVzIiksDQogICAgICAgICAgICAgYWVzKHk9aWZlbHNlKHNwZWNpbWVuPT0iQ01OSCA2MDkwIiwubG93ZXItOCwNCiAgICAgICAgICAgICAgICAgaWZlbHNlKHNwZWNpbWVuPT0iQ01OSCA3MDU0IiwubG93ZXIrOCwubG93ZXIpKSwNCiAgICAgICAgICAgICAgICAgeD0wLGxhYmVsPXJvdW5kKC5sb3dlciwxKSksc2l6ZT0zKSsNCiAgYW5ub3RhdGUoInRleHQiLHg9OTAseT00NzUsbGFiZWw9IjQuNiBtIixjb2xvcj0icmVkIikrDQogIGdlb21fdGV4dChkYXRhPS4lPiVmaWx0ZXIoISFmb3NzaWwuc3BlY2ltZW5zLGdlbnVzPT0iRHVua2xlb3N0ZXVzIiksDQogICAgICAgICAgICBhZXMobGFiZWw9c3BlY2ltZW4sDQogICAgICAgICAgICAgICAgeD1pZmVsc2Uoc3BlY2ltZW49PSJDTU5IIDcwNTQiLE9PTCsxLE9PTCksDQogICAgICAgICAgICAgICAgeT1pZmVsc2Uoc3BlY2ltZW49PSJDTU5IIDYwOTAiLC5sb3dlci0xOCwubG93ZXIpKSxoanVzdD0wLG51ZGdlX3k9LTEzKQ0KYGBgDQoNCk5vdGUgdGhhdCBpbiBvcmRlciB0byBwcm9kdWNlIGxlbmd0aHMgb2YgZ3JlYXRlciB0aGFuIDUgbSBmb3IgbGFyZ2UsIGFkdWx0IGluZGl2aWR1YWxzIG9mICpEdW5rbGVvc3RldXMgdGVycmVsbGkqIChpLmUuLCB0aGUgdmVyeSB1cHBlcm1vc3QgcGFydCBvZiB0aGUgcHJlZGljdGlvbiBpbnRlcnZhbCBpbiB0aGlzIGdyYXBoKSByZXF1aXJlcyAqRHVua2xlb3N0ZXVzKiB0byBwbG90IGluIGEgcmVnaW9uIG9mIHRoZSBncmFwaCB3aGVyZSB0aGVyZSBhcmUgZmV3IGZpc2hlcyBpbiBnZW5lcmFsLiBNb3N0IG9mIHRoZSBmaXNoZXMgdGhhdCBhcmUgaW4gdGhpcyByZWdpb24gb2YgdGhlIGdyYXBoIGFyZSBlaXRoZXIgYW5ndWlsbGlmb3JtIHRheGEgb3Igb25lcyB3aXRoIGhpZ2hseSBlbG9uZ2F0ZSBjYXVkYWwgZmlucyBzdWNoIGFzICpBbG9waWFzKi4gSW5kZWVkLCBtb3N0IG9mIHRoZSBsYXJnZSBmaXNoZXMgaW4gdGhlIHVwcGVyIHJlZ2lvbiBvZiB0aGlzIGdyYXBoIHRlbmQgdG8gYmUgdGhvc2Ugd2l0aCBoaWdobHkgZWxvbmdhdGUgYm9kaWVzIHN1Y2ggYXMgKlRldHJhcHR1cnVzKi4NCg0KIyBFc3RpbWF0aW5nIHRvdGFsIGxlbmd0aCBpbiAqRHVua2xlb3N0ZXVzIHRlcnJlbGxpKiB1bmRlciBkaWZmZXJlbnQgbW9kZWxzDQoNCkZvciB0aGlzIHNlY3Rpb24sIGxlbmd0aCBlc3RpbWF0ZXMgd2lsbCBwcmltYXJpbHkgZm9jdXMgb24gZm91ciBzcGVjaW1lbnM6IENNTkggNzQyNCwgQ01OSCA2MDkwLCBDTU5IIDcwNTQsIGFuZCBDTU5IIDU3NjguIE9PTCBpcyBtZWFzdXJhYmxlIGluIGEgbnVtYmVyIG9mIG90aGVyIHNwZWNpbWVucyBvZiAqRHVua2xlb3N0ZXVzIHRlcnJlbGxpKiwgYnV0IHRoZSBmb3VyIHNwZWNpbWVucyBsaXN0ZWQgaGVyZSBhcmUga25vd24gZnJvbSBjb21wbGV0ZSwgbW91bnRlZCBoZWFkcyB0aGF0IHBlcnRhaW4gdG8gYSBzaW5nbGUgaW5kaXZpZHVhbCwgYW5kIHRodXMgYXJlIHRoZSBtb3N0IHVzZWZ1bCBhcyBhIHJlYWxpdHkgY2hlY2sgdG8gdGVzdCB0aGUgZWZmZWN0cyBvZiBkaWZmZXJlbnQgbW9kZWxzIG9uIHRoZSBzaXplIG9mICpELiB0ZXJyZWxsaSouDQoNCiMjIFByZWRpY3RpbmcgdXNpbmcgZnVzaWZvcm0gdGF4YSBvbmx5DQoNCk5vdGUsIGZvciB0aGlzIG1vZGVsIHRoZSBmYW1pbGllcyBTZXJyYW5pZGFlLCBNb25hY2FudGhpZGFlLCBhbmQgQmFsaXN0aWRhZSB3ZXJlIGV4Y2x1ZGVkLCBkdWUgdG8gdGhlc2UgZ3JvdXBzIHNob3dpbmcgYXBvbW9ycGhpYyBzcGVjaWFsaXphdGlvbnMgaW4gc2t1bGwgbW9ycGhvbG9neSAoZWl0aGVyIGFuIGVubGFyZ2VkIGhlYWQgaW4gU2VycmFuaWRhZSBvciBhIHBvc3Rlcmlvcmx5IHNoaWZ0ZWQgb3JiaXQgaW4gQmFsaXN0b2lkZWkpIHdoaWNoIHN1Z2dlc3RzIHRoZXkgdmlvbGF0ZSB0aGUgYXNzdW1wdGlvbnMgb2YgY29uc2lzdGVudCBoZWFkLWJvZHkgcHJvcG9ydGlvbnMgc2VlbiBpbiBhbGwgb3RoZXIgZnVzaWZvcm0gZmlzaGVzLg0KDQpgYGB7cn0NCmZpdC5mdXNpZm9ybTwtbG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCksDQogICAgICAgICAgICAgICAgIGRhdGFfZmluYWwlPiUNCiAgICAgICAgICAgICAgICAgICBmaWx0ZXIoc2hhcGUgJWluJSBjKCJmdXNpZm9ybSIpLGxlbmd0aF9hcyE9ImVzdGltYXRlZCB0LmwuIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIWZhbWlseSAlaW4lIGMoIlNlcnJhbmlkYWUiLCJIb2xvY2VudHJpZGFlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk1vbmFjYW50aGlkYWUiLCJCYWxpc3RpZGFlIiwiIikpKQ0KDQpzdW1tYXJ5KGZpdC5mdXNpZm9ybSkNCnJlZ3Jlc3Npb24uc3RhdHMoZml0LmZ1c2lmb3JtKQ0KDQpwcmVkaWN0LmZ1c2lmb3JtPC1mb3NzaWxfdGF4YSU+JQ0KICBkcm9wX25hKE9PTCklPiUNCiAgZmlsdGVyKCEhZm9zc2lsLnNwZWNpbWVucyklPiUNCiAgYXVnbWVudChmaXQuZnVzaWZvcm0sbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0aW9uIiklPiUNCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0LmZ1c2lmb3JtKSRDRiksDQogICAgICAgICBQRT0oLmZpdHRlZC10b3RhbF9sZW5ndGgpLy5maXR0ZWQsDQogICAgICAgICAuUEU9cGFzdGUwKCIoIixyb3VuZCguZml0dGVkKigxLQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoZml0LmZ1c2lmb3JtKSRhZGpQRS8xMDApLDEpLA0KICAgICAgICAgICAgICAgICAgICAi4oCTIixyb3VuZCguZml0dGVkKigxKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoZml0LmZ1c2lmb3JtKSRhZGpQRS8xMDApLDEpLCIpIiksDQogICAgICAgICAucmFuZ2U9cGFzdGUwKCIoIixyb3VuZCgubG93ZXIsMSksDQogICAgICAgICAgICAgICAgICAgICAgICLigJMiLHJvdW5kKC51cHBlciwxKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIikiKSklPiUNCiAgYXJyYW5nZSguZml0dGVkKSU+JQ0KICBzZWxlY3QodGF4b24sc3BlY2ltZW4sdG90YWxfbGVuZ3RoLC5maXR0ZWQsLlBFLC5yYW5nZSxQRSkNCnByZWRpY3QuZnVzaWZvcm0lPiUNCiAga2FibGUoZGlnaXRzPWMoMSwxLDEsMSwxLDEsMyksDQogICAgICAgIGNhcHRpb249ICJMZW5ndGggZXN0aW1hdGVzIGZvciBzcGVjaW1lbnMgb2YgPGk+RHVua2xlb3N0ZXVzIHRlcnJlbGxpPC9pPiB1c2luZyBhIG1vZGVsIGJhc2VkIG9uIG9ubHkgZnVzaWZvcm0gZmlzaGVzIiwNCiAgICAgICAgY29sLm5hbWVzID0gYygiVGF4b24iLCJTcGVjaW1lbiIsIkFjdHVhbCBMZW5ndGgiLCJFc3QuIiwiKy8tICVQRSIsIjk1JSBQLkkuIiwiJVBFIikpJT4lDQogIGNvbHVtbl9zcGVjKDEsIGl0YWxpYyA9IFQpJT4lDQogIGNvbHVtbl9zcGVjKDQsIGJvbGQgPSBUKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQpgYGB7cn0NCnJiaW5kKCIlUEUgKGFsbCBzcGVjaW1lbnMpIj1wcmVkaWN0LmZ1c2lmb3JtJT4lDQogICAgZHJvcF9uYSh0b3RhbF9sZW5ndGgpJT4lDQogICAgbXV0YXRlKFBFPSgoLmZpdHRlZC10b3RhbF9sZW5ndGgpLy5maXR0ZWQpJT4lcm91bmQoNCkpJT4lDQogICAgcHVsbChQRSklPiVhYnMoKSU+JW1lYW4oKSwNCiAgIiVQRSAoZXhjbHVkaW5nIHVudXN1YWwgc3BlY2ltZW5zKSI9cHJlZGljdC5mdXNpZm9ybSAlPiUNCiAgICBkcm9wX25hKHRvdGFsX2xlbmd0aCklPiUNCiAgICBmaWx0ZXIoIXRheG9uICVpbiUgYygiQW1hemljaHRoeXMgdHJpbmFqc3RpY2FlIiwiSG9sb25lbWEgd2VzdG9sbGkiKSwNCiAgICAgICAgICAgc3BlY2ltZW4hPSJOSE1VSyBQViBQIDQ5NjYzIiklPiUNCiAgICBtdXRhdGUoUEU9KCguZml0dGVkLXRvdGFsX2xlbmd0aCkvLmZpdHRlZCklPiVyb3VuZCg0KSklPiUNCiAgICBwdWxsKFBFKSU+JWFicygpJT4lbWVhbigpKSU+JQ0KICBrYWJsZShkaWdpdHM9NCxjb2wubmFtZXM9IlBFIixhbGlnbj0iYyIsDQogICAgICAgIGNhcHRpb249Ik1lYW4gcGVyY2VudCBlcnJvciBmb3IgbGVuZ3RoIGVzdGltYXRpb25zIG9mIGNvbXBsZXRlIGFydGhyb2RpcmVzIHVzaW5nIE9PTCBhbmQgYSBtb2RlbCBiYXNlZCBzb2xlbHkgb24gZnVzaWZvcm0gZmlzaGVzIiwNCiAgICAgICAgdGFibGUuYXR0ciA9ICJzdHlsZT0nd2lkdGg6NzAlOyciKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQoiVW51c3VhbCBzcGVjaW1lbnMiIGluIHRoaXMgY2FzZSByZWZlcnMgdG8gKkhvbG9uZW1hIHdlc3RvbGxpKiwgKkFtYXppY2h0aHlzIHRyaW5hanN0aWNhZSosIGFuZCBvbmUgc3BlY2ltZW4gb2YgKkRpY2tvc3RldXMgdGhyZWlwbGFuZGkqIChOSE1VSyBQViBQIDQ5NjYzKS4NCg0KIDEuIEFsdGhvdWdoIHRoZSByZWNvbnN0cnVjdGlvbiBvZiAqSC4gd2VzdG9sbGkqIGZyb20gVHJpbmFqc3RpYyAoMTk5OSkgaXMgdXNlZCBoZXJlIGJlY2F1c2Ugb2YgdGhlIGxpbWl0ZWQgZGF0YSwgaXQgaXMgbm90IGNsZWFyIGlmIHRoZSBoZWFkLXRydW5rIHByb3BvcnRpb25zIG9mIHRoaXMgc3BlY2ltZW4gYXJlIGNvcnJlY3QuIE9uZSBzcGVjaW1lbiB0aGF0IHByZXNlcnZlcyBhIG5lYXItY29tcGxldGUgdGFpbCAoV0FNIDk2LjYuMTExQSkgc2VlbXMgdG8gbWVhc3VyZSBhcHByb3hpbWF0ZWx5IDIxLTIzIGNtIHBvc3RlcmlvciB0byB0aGUgYXJtb3IuIEhvd2V2ZXIsIGl0IGlzIHVuY2xlYXIgaG93IHRoaXMgc3BlY2ltZW4gc2NhbGVzIHdpdGggdGhlIGFudGVyaW9yIGhhbGYgb2YgdGhlIGFuaW1hbCwgYXMgdGhlIG9ubHkgcGxhdGUgcHJlc2VydmVkIGlzIGEgcGFydGlhbCBwb3N0ZXJvdmVudHJvbGF0ZXJhbC4gVGh1cywgaXQgaXMgcG9zc2libGUgdGhpcyBhbmltYWwgaGFkIGEgc2hvcnRlciBwb3N0LXRob3JhY2ljIHJlZ2lvbiwgd2hpY2ggd291bGQgYnJpbmcgaXQgbW9yZSBpbiBsaW5lIHdpdGggdGhlIHByb3BvcnRpb25zIHNlZW4gaW4gb3RoZXIgYXJ0aHJvZGlyZXMuDQogMi4gKkFtYXppY2h0aHlzIHRyaW5hanN0aWNhZSogW3Nob3dzIHVudXN1YWwgaGVhZC1ib2R5IHByb3BvcnRpb25zIGNvbXBhcmVkIHRvIG90aGVyIGFydGhyb2RpcmVzLCBdKCNBbWF6aWNodGh5cyksIGFuZCBoZW5jZSBpdCBpcyBwb3NzaWJsZSB0aGF0IHRoaXMgc3BlY2ltZW4gaXMgYW4gb3V0bGllciBjb21wYXJlZCB0byBvdGhlciBtZW1iZXJzIG9mIHRoZSBncm91cC4NCiAzLiBUaGUgaGVhZCByZWdpb24gb2YgTk1IVUsgUFYgUCA0OTk2MyBpcyBwb29ybHkgcHJlc2VydmVkIGFuZCBpdCBtYXkgYmUgdGhhdCBPT0wgaXMgbWlzLW1lYXN1cmVkIGluIHRoaXMgc3BlY2ltZW4uIFRoZSByb3VnaCBkaW1lbnNpb25zIG9mIHRoZSBoZWFkIGNhbiBiZSBzb21ld2hhdCBkZWZpbmVkIGJ1dCBtYW55IG9mIGl0cyBmZWF0dXJlcyBhcmUgZGlzdG9ydGVkIChlLmcuLCBsYXRlcmFsIGxpbmVzICksIGFuZCBpdCBtYXkgYmUgdGhhdCB0aGUgbG9jYXRpb24gb2YgdGhlIGNyYW5pby10aG9yYWNpYyBqb2ludCBpcyBub3QgYWNjdXJhdGVseSBpZGVudGlmaWVkLiBUaGlzIGlzIHN1cHBvcnRlZCBieSB0aGUgZmFjdCB0aGF0IGl0cyBoZWFkIHByb3BvcnRpb25zIGRpZmZlciBncmVhdGx5IGZyb20gdGhlIG90aGVyIHNwZWNpbWVuIG9mICpELiB0aHJlaXBsYW5kaSogY29uc2lkZXJlZCBoZXJlIChOTVMgMTk4Ny43LjExOCkuDQoNCiMjIyBHcmFwaCBvZiBmdXNpZm9ybSBmaXNoZXMgb25seSB7I2Z1c2lmb3JtZ3JhcGh9DQoNCihyZWY6ZnVzaWZvcm0pIFBsb3Qgb2Ygb3JiaXQtb3BlcmN1bGFyIGxlbmd0aCBhZ2FpbnN0IHRvdGFsIGxlbmd0aCBvbiBhIGxvZzxzdWI+MTA8L3N1Yj4gc2NhbGUgaW4gZnVzaWZvcm0gZmlzaGVzLiBHcm91cGVycyAoU2VycmFuaWRhZSksIHdoaWNoIHNob3cgYSBzaGlmdCBpbiB0aGUgeS1pbnRlcmNlcHQgb2YgdGhlaXIgcmVncmVzc2lvbiBsaW5lIGR1ZSB0byBoYXZpbmcgZGlzcHJvcG9ydGlvbmF0ZWx5IGxhcmdlIGhlYWRzLCBhcmUgZGVub3RlZCBpbiBncmF5LiBCYWxpc3RvaWRlaSwgd2hpY2ggYXJlIGFsc28gZXhjbHVkZWQgYmVjYXVzZSBvZiBhbiBhcG9tb3JwaGljIHBvc3RlcmlvciBzaGlmdCBpbiB0aGUgb3JiaXQsIGFyZSBkZW5vdGVkIGluIHdoaXRlLg0KDQpgYGB7cixmaWcuY2FwPSIocmVmOmZ1c2lmb3JtKSJ9DQpnZ3Bsb3QoZGF0YV9maW5hbCU+JQ0KICAgICAgICAgY2JpbmQoZXhwKHByZWRpY3QobG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhX2ZpbmFsJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihzaGFwZSA9PSAiZnVzaWZvcm0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoX2FzIT0iZXN0aW1hdGVkIHQubC4iKSksDQogICAgICAgICAgICAgICAgICAgICAgIGRhdGFfZmluYWwsaW50ZXJ2YWw9InByZWRpY3Rpb24iKSkpJT4lDQogICAgICAgICBmaWx0ZXIoc2hhcGU9PSJmdXNpZm9ybSIsDQogICAgICAgICAgICAgICAgbGVuZ3RoX2FzIT0iZXN0aW1hdGVkIHQubC4iKSwNCiAgICAgICBhZXMoeT10b3RhbF9sZW5ndGgseD1PT0wpKSsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3I9Y2xhZGUsc2hhcGU9Y2xhZGUpKSsNCiAgZ2VvbV9wb2ludChhZXMoZmlsbD1jbGFkZSxzaGFwZT1jbGFkZSksc2l6ZT0yKSsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSJsbSIsZm9ybXVsYT15fngpKw0KICBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzPWMoMjEsMjQsMjIsMjMpKSsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCJyZWQiLCJncmVlbiIsTkEsTkEpLG5hLnZhbHVlPU5BKSsNCiAgZ2VvbV9wb2ludChkYXRhPWRhdGFfZmluYWwlPiVmaWx0ZXIoZmFtaWx5PT0iU2VycmFuaWRhZSIpLA0KICAgICAgICAgICAgIHNoYXBlPTIxLHNpemU9MixmaWxsPSJncmF5IikrDQogIGdlb21fcG9pbnQoZGF0YT1kYXRhX2ZpbmFsJT4lZmlsdGVyKGZhbWlseSAlaW4lIGMoIk1vbmFjYW50aGlkYWUiLCJCYWxpc3RpZGFlIikmDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaGFwZT09ImZ1c2lmb3JtIiksDQogICAgICAgICAgICAgc2hhcGU9MjEsc2l6ZT0yLGZpbGw9IndoaXRlIikrDQogIGdlb21fc21vb3RoKGRhdGE9ZGF0YV9maW5hbCU+JWZpbHRlcihmYW1pbHk9PSJTZXJyYW5pZGFlIiksDQogICAgICAgICAgICAgZm9ybXVsYT15fngsbWV0aG9kPSJsbSIsY29sb3I9ImJsYWNrIikrDQogIGdlb21fbGluZShhZXMoeT1sd3IpLCBjb2xvciA9ICJibHVlIiwgbGluZXR5cGUgPSAiZGFzaGVkIikrDQogIGdlb21fbGluZShhZXMoeT11cHIpLCBjb2xvciA9ICJibHVlIiwgbGluZXR5cGUgPSAiZGFzaGVkIikrDQogIGxhYnMoeD0iT3JiaXQtT3BlcmN1bGFyIExlbmd0aCAoY20pIix5PSJUb3RhbCBMZW5ndGggKGNtKSIsDQogICAgICAgY29sb3I9IkNsYWRlIixzaGFwZT0iQ2xhZGUiLGZpbGw9IkNsYWRlIikrDQogIHNjYWxlX3hfY29udGludW91cyh0cmFucz0nbG9nMTAnKSsNCiAgc2NhbGVfeV9jb250aW51b3VzKHRyYW5zPSdsb2cxMCcpKw0KICB0aGVtZV9jbGFzc2ljKCkrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj1jKDAuOCwwLjI1KSkNCmBgYA0KDQpgYGB7fQ0KZ2dwbG90KGRhdGFfZmluYWwlPiUNCiAgICAgICAgIGNiaW5kKGV4cChwcmVkaWN0KGxtKGxvZyh0b3RhbF9sZW5ndGgpfmxvZyhPT0wpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YV9maW5hbCU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoc2hhcGUgPT0gImZ1c2lmb3JtIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlbmd0aF9hcyE9ImVzdGltYXRlZCB0LmwuIikpLA0KICAgICAgICAgICAgICAgICAgICAgICBkYXRhX2ZpbmFsLGludGVydmFsPSJwcmVkaWN0aW9uIikpKSU+JQ0KICAgICAgICAgZmlsdGVyKHNoYXBlPT0iZnVzaWZvcm0iLA0KICAgICAgICAgICAgICAgIGxlbmd0aF9hcyE9ImVzdGltYXRlZCB0LmwuIiksDQogICAgICAgYWVzKHk9T09ML3RvdGFsX2xlbmd0aCx4PXRvdGFsX2xlbmd0aCkpKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvcj1jbGFkZSxzaGFwZT1jbGFkZSkpKw0KICBnZW9tX3BvaW50KGFlcyhmaWxsPWNsYWRlLHNoYXBlPWNsYWRlKSxzaXplPTIpKw0KICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIixmb3JtdWxhPXl+eCkrDQogIHNjYWxlX3NoYXBlX21hbnVhbCh2YWx1ZXM9YygyMSwyNCwyMiwyMykpKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoInJlZCIsImdyZWVuIixOQSxOQSksbmEudmFsdWU9TkEpKw0KICBnZW9tX3BvaW50KGRhdGE9ZGF0YV9maW5hbCU+JWZpbHRlcihmYW1pbHk9PSJTZXJyYW5pZGFlIiksDQogICAgICAgICAgICAgc2hhcGU9MjEsc2l6ZT0yLGZpbGw9ImdyYXkiKSsNCiAgZ2VvbV9wb2ludChkYXRhPWRhdGFfZmluYWwlPiVmaWx0ZXIoZmFtaWx5ICVpbiUgYygiTW9uYWNhbnRoaWRhZSIsIkJhbGlzdGlkYWUiKSYNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNoYXBlPT0iZnVzaWZvcm0iKSwNCiAgICAgICAgICAgICBzaGFwZT0yMSxzaXplPTIsZmlsbD0id2hpdGUiKSsNCiAgZ2VvbV9zbW9vdGgoZGF0YT1kYXRhX2ZpbmFsJT4lZmlsdGVyKGZhbWlseT09IlNlcnJhbmlkYWUiKSwNCiAgICAgICAgICAgICBmb3JtdWxhPXl+eCxtZXRob2Q9ImxtIixjb2xvcj0iYmxhY2siKSsNCiAgZ2VvbV9saW5lKGFlcyh5PWx3ciksIGNvbG9yID0gImJsdWUiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSsNCiAgZ2VvbV9saW5lKGFlcyh5PXVwciksIGNvbG9yID0gImJsdWUiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSsNCiAgbGFicyh4PSJUb3RhbCBMZW5ndGggKGNtKSIseT0iUGVyY2VudCBPT0wiLA0KICAgICAgIGNvbG9yPSJDbGFkZSIsc2hhcGU9IkNsYWRlIixmaWxsPSJDbGFkZSIpKw0KICBzY2FsZV94X2NvbnRpbnVvdXModHJhbnM9J2xvZzEwJykrDQogIHNjYWxlX3lfY29udGludW91cyh0cmFucz0nbG9nMTAnKSsNCiAgdGhlbWVfY2xhc3NpYygpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249YygwLjgsMC4yNSkpDQpgYGANCg0KIyMjIEVmZmVjdCBvZiBncm91cGVycyAoU2VycmFuaWRhZSkNCg0KIyMjIyBPT0wgaW4gZ3JvdXBlcnMgcmVsYXRpdmUgdG8gb3RoZXIgZmlzaGVzDQoNCihyZWY6ZnVzaWZvcm1wZXJjZW50T09MKSBPT0wgZ3JhcGhlZCBhcyBhIHBlcmNlbnRhZ2Ugb2YgdG90YWwgbGVuZ3RoIGluIGZ1c2lmb3JtIGZpc2hlcywgc2hvd2luZyBob3cgZ3JvdXBlcnMgKFNlcnJhbmlkYWUpLCBwYXJ0aWN1bGFybHkgbW9yZSBkZXJpdmVkIGdyb3VwZXJzIG9mIHRoZSAqQ2VwaGFsb3Bob2xpcyotKkVwaW5lcGhlbHVzKiBjbGFkZSBhbmQgbm90IG1vcmUgYmFzYWwgZ3JvdXBlciBsaWtlICpWYXJpb2xhKiBhbmQgKlBsZWN0cm9wb211cyosIGhhdmUgbXVjaCBsYXJnZXIgT09MIHRoYW4gd291bGQgYmUgZXhwZWN0ZWQgZm9yIGZ1c2lmb3JtIHRheGEsIGFuZCB0YXhhIHdpdGggcG9zdGVyaW9ybHkgc2hpZnRlZCBvcmJpdHMgKEJhbGlzdG9pZGVpKSBoYXZlIG11Y2ggc21hbGxlciBPT0wgdGhhbiBleHBlY3RlZC4NCg0KYGBge3IsZmlnLmNhcD0iKHJlZjpmdXNpZm9ybXBlcmNlbnRPT0wpIixmaWcud2lkdGg9OSxmaWcuYXNwPTAuNjV9DQpnZ3Bsb3QoZGF0YV9maW5hbCU+JQ0KICAgICAgICAgZHJvcF9uYShPT0wsdG90YWxfbGVuZ3RoKSU+JQ0KICAgICAgICAgZmlsdGVyKHNoYXBlPT0iZnVzaWZvcm0iLA0KICAgICAgICAgICAgICAgIGxlbmd0aF9hcyE9ImVzdGltYXRlZCB0LmwuIiklPiUNCiAgICAgICAgIG11dGF0ZShjbGFkZT1jYXNlX3doZW4oY2xhZGU9PSJQbGFjb2Rlcm1pIn4iUGxhY29kZXJtaSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGNsYWRlPT0iU2FyY29wdGVyeWdpaSJ+IlNhcmNvcHRlcnlnaWkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBmYW1pbHkgJWluJSBjKCJMYW1uaWRhZSIsIk1lZ2FjaGFzbWF0aWRhZSIpfiJMYW1uaWRhZSArIE1lZ2FjaGFzbWF0aWRhZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGNsYWRlPT0iQ2hvbmRyaWNodGh5ZXMifiJDaG9uZHJpY2h0aHllcyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGZhbWlseT09IlNlcnJhbmlkYWUifiJTZXJyYW5pZGFlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZmFtaWx5ICVpbiUgYygiTW9uYWNhbnRoaWRhZSIsIkJhbGlzaWRhZSIpfiJCYWxpc3RvaWRlaSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGNsYWRlPT0iQWN0aW5vcHRlcnlnaWkifiJBY3Rpbm9wdGVyeWdpaSwgT3RoZXIiKSklPiUNCiAgICAgICAgIGFycmFuZ2UoY2xhZGUpLA0KICAgICAgIGFlcyh5PU9PTC90b3RhbF9sZW5ndGgseD10b3RhbF9sZW5ndGgpKSsNCiAgZ2VvbV9zdGFyKGFlcyhjb2xvcj1jbGFkZSxzdGFyc2hhcGU9Y2xhZGUpKSsNCiAgZ2VvbV9zdGFyKGFlcyhmaWxsPWNsYWRlLHN0YXJzaGFwZT1jbGFkZSksc2l6ZT0yKSsNCiAgZ2VvbV9zdGFyKGFlcyhmaWxsPWNsYWRlLHN0YXJzaGFwZT1jbGFkZSksc2l6ZT0zLA0KICAgICAgICAgICAgZGF0YT0uJT4lIGZpbHRlcihjbGFkZT09IlBsYWNvZGVybWkiKSkrDQogIGdlb21fc21vb3RoKG1ldGhvZD0ibG0iLGZvcm11bGE9eX54KSsNCiAgc2NhbGVfc3RhcnNoYXBlX21hbnVhbCh2YWx1ZXM9YygxNSwxNSwxMywxMywxLDE1LDE1KSkrDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKGh1ZV9wYWwoKSg1KVsxXSwid2hpdGUiLGh1ZV9wYWwoKSg1KVstMV0sImdyYXkiKSkrDQogIGdlb21fc21vb3RoKGRhdGE9ZGF0YV9maW5hbCU+JWZpbHRlcihmYW1pbHk9PSJTZXJyYW5pZGFlIiksDQogICAgICAgICAgICAgIGZvcm11bGE9eX54LG1ldGhvZD0ibG0iLGNvbG9yPSJibGFjayIpKw0KICBsYWJzKHg9IlRvdGFsIExlbmd0aCAoY20pIix5PSJPT0wgKGFzIHBlcmNlbnQgb2YgdG90YWwgbGVuZ3RoKSIsDQogICAgICAgY29sb3I9IkNsYWRlIixzdGFyc2hhcGU9IkNsYWRlIixmaWxsPSJDbGFkZSIpKw0KICBzY2FsZV94X2NvbnRpbnVvdXModHJhbnM9J2xvZzEwJykrDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHM9c2NhbGVzOjpsYWJlbF9wZXJjZW50KGFjY3VyYWN5ID0gMSkpKw0KICB0aGVtZV9jbGFzc2ljKCkNCmBgYA0KDQojIyMjIFRlc3RpbmcgaWYgZ3JvdXBlcnMgc2hvdyBhIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IGludGVyY2VwdCB0aGFuIG90aGVyIGZpc2hlcw0KDQpgYGB7cn0NCmRhdGFfZmluYWwlJCUNCiAgbG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCkrKGZhbWlseT09IlNlcnJhbmlkYWUiKSwuKSU+JQ0KICBzdW1tYXJ5KCkNCmRhdGFfZmluYWwlJCUNCiAgbG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKGhlYWRfbGVuZ3RoKSsoZmFtaWx5PT0iU2VycmFuaWRhZSIpLC4pJT4lDQogIHN1bW1hcnkoKQ0KYGBgDQoNCkJhc2VkIG9uIHRoaXMsIGdyb3VwZXJzIGhhdmUgYSBzaWduaWZpY2FudGx5IGxvd2VyIGludGVyY2VwdCAoaS5lLiwgbGFyZ2VyIGhlYWQvT09MIHJlbGF0aXZlIHRvIHRvdGFsIGxlbmd0aCkgdGhhbiBpcyB0eXBpY2FsIGZvciBmaXNoZXMuDQoNCiMjIyMgVGVzdGluZyB3aGV0aGVyIGdyb3VwZXJzIGhhdmUgbGFyZ2VyIE9PTCB0aGFuIG90aGVyIGZpc2hlcyB3aGVuIGJvZHkgc2hhcGUgaXMgYWNjb3VudGVkIGZvcg0KDQpgYGB7cn0NCmRhdGFfZmluYWwlJCUNCiAgbG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCkrc2hhcGUrKGZhbWlseT09IlNlcnJhbmlkYWUiKSwuKSU+JQ0KICBzdW1tYXJ5KCkNCmBgYA0KDQpFdmVuIGlmIGFjY291bnRpbmcgZm9yIHNoYXBlLCBncm91cGVyIHN0aWxsIHN0YW5kIG91dCBhcyBhIG1ham9yIG91dGxpZXIuDQoNCiMjIyMgU3VtbWFyeSBvZiBzbG9wZXMgdGVzdCBiZXR3ZWVuIGdyb3VwZXJzIGFuZCBvdGhlciBmdXNpZm9ybSBmaXNoZXMNCiAgDQpgYGB7cn0NCmRhdGFfZmluYWwgJT4lDQogIGZpbHRlcihzaGFwZT09ImZ1c2lmb3JtIikgJSQlDQogIGxtKGxvZyh0b3RhbF9sZW5ndGgpfmxvZyhPT0wpKihmYW1pbHk9PSJTZXJyYW5pZGFlIiksLiklPiUNCiAgc3VtbWFyeSgpDQpgYGANCg0KQmFzZWQgb24gdGhpcywgaXQgYXBwZWFycyBhcyB0aG91Z2ggZGVzcGl0ZSBzZXJyYW5pZHMgc2hvd2luZyBhIG11Y2ggc2lnbmlmaWNhbnRseSBsb3dlciBpbnRlcmNlcHQgKGkuZS4sIGxhcmdlciBoZWFkL09PTCkgdGhhbiBvdGhlciBmdXNpZm9ybSBmaXNoZXMsIHRoZSBhbGxvbWV0cmljIHJlbGF0aW9uc2hpcCAoc2xvcGUpIGJldHdlZW4gdGhlIHR3byBpcyB0aGUgc2FtZS4NCg0KIyMjIyBQcmVkaWN0aW5nIGJvZHkgbGVuZ3RocyBpbiBhcnRocm9kaXJlcyB1c2luZyBvbmx5IGdyb3VwZXJzDQoNCmBgYHtyLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KZml0Lmdyb3VwZXI8LWxtKGxvZyh0b3RhbF9sZW5ndGgpfmxvZyhPT0wpLA0KICAgICAgICAgICAgICAgIGRhdGFfZmluYWwlPiVmaWx0ZXIoZmFtaWx5PT0iU2VycmFuaWRhZSIpKQ0KKHByZWRpY3QoZml0Lmdyb3VwZXIsDQogICAgICAgIGZvc3NpbF90YXhhJT4lDQogICAgICAgICAgZmlsdGVyKCFpcy5uYShPT0wpLGNsYWRlPT0iUGxhY29kZXJtaSIsDQogICAgICAgICAgICAgICAgIGdlbnVzPT0iRHVua2xlb3N0ZXVzIiZzcGVjaW1lbiVpbiUgYygiQ01OSCA3MDU0IiwiQ01OSCA2MDkwIiwiQ01OSCA1NzY4IiwiQ01OSCA3NDI0Iil8bGVuZ3RoX2FzPT0idG90YWwgbGVuZ3RoIiksDQogICAgICAgIGludGVydmFsPSJwcmVkaWN0aW9uIiklPiUNCiAgZXhwKCkqcmVncmVzc2lvbi5zdGF0cyhmaXQuZ3JvdXBlcikkQ0YpJT4lDQogIGRhdGEuZnJhbWUoKSU+JXJvd25hbWVzX3RvX2NvbHVtbigpJT4lDQogIGFycmFuZ2UoZml0KSU+JQ0KICBsZWZ0X2pvaW4oZm9zc2lsX3RheGElPiVyb3duYW1lc190b19jb2x1bW4oKSxieT0icm93bmFtZSIpJT4lDQogIG11dGF0ZShlc3RfT09MPWV4cChwcmVkaWN0KGZpdC5mdXNpZm9ybSwuKSkqcmVncmVzc2lvbi5zdGF0cyhmaXQuZnVzaWZvcm0pJENGLA0KICAgICAgICAgdG90YWxfbGVuZ3RoPWlmZWxzZShsZW5ndGhfYXM9PSJlc3RpbWF0ZWQgdC5sLiIsTkEsdG90YWxfbGVuZ3RoKSklPiUNCiAgc2VsZWN0KHRheG9uLHNwZWNpbWVuLHRvdGFsX2xlbmd0aCxlc3RfT09MLGZpdCxsd3IsdXByKSU+JQ0KICBrYWJsZShkaWdpdHM9MSxhbGlnbj1jKCJsIiwiYyIsImMiLCJjIiwiYyIsImMiLCJjIiksDQogICAgICAgIGNhcHRpb249ICJMZW5ndGggZXN0aW1hdGVzIGZvciBzcGVjaW1lbnMgb2YgPGk+RHVua2xlb3N0ZXVzIHRlcnJlbGxpPC9pPiB1c2luZyBhIG1vZGVsIGJhc2VkIG9uIG9ubHkgZ3JvdXBlcnMgKFNlcnJhbmlkYWUpIiwNCiAgICAgICAgY29sLm5hbWVzID0gYygiVGF4b24iLCJTcGVjaW1lbiIsIkFjdHVhbCBMZW5ndGgiLCJFc3RpbWF0ZWQgTGVuZ3RoIFVzaW5nIEFsbCBGdXNpZm9ybSBUYXhhIiwiRXN0LiIsIkx3ciA5NSUgUC5JLiIsIlVwciA5NSUgUC5JLiIpKSU+JQ0KICBjb2x1bW5fc3BlYygxLCBpdGFsaWMgPSBUKSU+JQ0KICBjb2x1bW5fc3BlYyg1LCBib2xkID0gVCklPiUNCiAgYWRkX2hlYWRlcl9hYm92ZShjKCIgIj00LCJFc3RpbWF0ZWQgVXNpbmcgU2VycmFuaWRhZSI9MykpJT4lDQogIGthYmxlX3N0eWxpbmcoKQ0KYGBgDQoNCkFzIGNhbiBiZSBzZWVuIGJ5IHRoZXNlIHJlc3VsdHMsIHVzaW5nIGdyb3VwZXJzIChTZXJyYW5pZGFlKSByZXN1bHRzIGluIG11Y2ggc2hvcnRlciBsZW5ndGhzIGZvciBhcnRocm9kaXJlcywgc3BlY2lmaWNhbGx5IGJlY2F1c2Ugc2VycmFuaWRzIGhhdmUgZGlzcHJvcG9ydGlvbmF0ZWx5IGxhcmdlIGhlYWRzIHJlbGF0aXZlIHRvIHRoZWlyIGJvZHkgc2l6ZSBjb21wYXJlZCB0byBvdGhlciBmaXNoZXMuIFRodXMsIGFsdGhvdWdoIGdyb3VwZXJzIGhhdmUgYSBsYXJnZWx5IGZ1c2lmb3JtIGJvZHkgcGxhbiwgaXQgc2VlbXMgcmVhc29uYWJsZSB0byBleGNsdWRlIHRoZW0gZnJvbSB0aGUgZnVzaWZvcm0gZmlzaGVzLW9ubHkgbW9kZWwgYmVjYXVzZSBvZiB0aGVpciB1bnVzdWFsIHByb3BvcnRpb25zLg0KDQojIyMgUGxvdHRpbmcgZXN0aW1hdGVkIGxlbmd0aHMgZm9yICpEdW5rbGVvc3RldXMqIHVzaW5nIGEgZnVzaWZvcm0tb25seSBtb2RlbA0KDQoocmVmOmZ1c2lmb3JtbGVuZ3RocykgTWluaW11bSBhbmQgbWF4aW11bSBlc3RpbWF0ZWQgbGVuZ3RoIGluIHNwZWNpbWVucyBvZiAqRHVua2xlb3N0ZXVzIHRlcnJlbGxpKiBjb25zaWRlcmVkIGluIHRoaXMgc3R1ZHkgdXNpbmcgb3JiaXQtb3BlcmN1bGFyIGxlbmd0aCwgdXNpbmcgYW4gZXF1YXRpb24gb25seSBjb25zaWRlcmluZyBmdXNpZm9ybSB0YXhhIChpLmUuLCBleGNsdWRpbmcgdGF4YSB3aXRoIHZlcnkgZWxvbmdhdGUgYm9keSBzaGFwZXMgbGlrZSBiYXJyYWN1ZGFzKS4gUGxvdCBpcyB6b29tZWQgaW4gdG8gZm9jdXMgb24gbGVuZ3RocyBpbiAqRHVua2xlb3N0ZXVzKi4gRXJyb3IgYmFycyByZXByZXNlbnQgOTUlIHByZWRpY3Rpb24gaW50ZXJ2YWxzLg0KDQpgYGB7cixmaWcuY2FwPSIocmVmOmZ1c2lmb3JtbGVuZ3RocykifQ0KZ2dwbG90KGRhdGFfZmluYWwlPiUNCiAgICAgICAgIGF1Z21lbnQoZml0LmZ1c2lmb3JtLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdCIpJT4lDQogICAgICAgICBtdXRhdGUoYWNyb3NzKC5maXR0ZWQ6LnVwcGVyLH5leHAoLikqcmVncmVzc2lvbi5zdGF0cyhmaXQuZnVzaWZvcm0pJENGKSklPiUNCiAgICAgICAgIGZpbHRlcihzaGFwZT09ImZ1c2lmb3JtIiksDQogICAgICAgYWVzKHk9dG90YWxfbGVuZ3RoLHg9T09MKSkrDQogICAgZ2VvbV9zdGFyKGFlcyhzdGFyc2hhcGU9Y2xhZGUpLGNvbG9yPSJkYXJrIGdyZXkiLGZpbGw9ImxpZ2h0IGdyZXkiKSsNCiAgICBzdGF0X3Ntb290aChnZW9tPSdsaW5lJyxhZXMoeT0uZml0dGVkKSwgY29sb3IgPSAiIzMzNjZGRiIsIG1ldGhvZD0ibG0iLCANCiAgICAgICAgICAgICAgICBhbHBoYT0wLjk1LCBzZT1GQUxTRSxzaXplPS43NSxmb3JtdWxhPXl+eCkrDQogICAgZ2VvbV9saW5lKGFlcyh5PS5sb3dlciksIGNvbG9yID0gIiMzMzY2RkYiLCBsaW5ldHlwZSA9ICJkYXNoZWQiLGFscGhhPTAuNSkrDQogICAgZ2VvbV9saW5lKGFlcyh5PS51cHBlciksIGNvbG9yID0gIiMzMzY2RkYiLCBsaW5ldHlwZSA9ICJkYXNoZWQiLGFscGhhPTAuNSkrDQogICAgZ2VvbV9lcnJvcmJhcihkYXRhPS4lPiVmaWx0ZXIoISFmb3NzaWwuc3BlY2ltZW5zLGdlbnVzPT0iRHVua2xlb3N0ZXVzIiksDQogICAgICAgICAgICAgICAgICBhZXMoeW1pbj0ubG93ZXIseW1heD0udXBwZXIpLHdpZHRoPTAuMSkrDQogICAgZ2VvbV9zdGFyKGRhdGE9LiU+JWZpbHRlcighIWZvc3NpbC5zcGVjaW1lbnMsZ2VudXM9PSJEdW5rbGVvc3RldXMiKSwNCiAgICAgICAgICAgICAgIGFlcyhzdGFyc2hhcGU9Y2xhZGUseT0uZml0dGVkKSxmaWxsPSJibGFjayIsc2l6ZT0yKSsNCiAgICBzY2FsZV9zdGFyc2hhcGVfbWFudWFsKHZhbHVlcz1jKDE1LDExLDEzLDEsMjgpLGd1aWRlPSJub25lIikrDQogICAgbGFicyh4PSJPcmJpdC1PcGVyY3VsYXIgTGVuZ3RoIChjbSkiLHk9IlRvdGFsIExlbmd0aCAoY20pIikrDQogIGNvb3JkX2NhcnRlc2lhbih5PWMoMCw1MDApLHg9YygwLDc1KSkrDQogIGdlb21fc2VnbWVudChkYXRhPS4lPiVmaWx0ZXIoZ2VudXM9PSJEdW5rbGVvc3RldXMiLCEhZm9zc2lsLnNwZWNpbWVucyksDQogICAgICAgICAgICAgICBhZXMoeT0ubG93ZXIseWVuZD0ubG93ZXIseD0tMjAseGVuZD1PT0wpLA0KICAgICAgICAgICAgICAgbGluZXR5cGU9ImRhc2hlZCIsYWxwaGE9MC43NSkrDQogIGdlb21fc2VnbWVudChkYXRhPS4lPiVmaWx0ZXIoZ2VudXM9PSJEdW5rbGVvc3RldXMiLCEhZm9zc2lsLnNwZWNpbWVucyksDQogICAgICAgICAgICAgICBhZXMoeT0udXBwZXIseWVuZD0udXBwZXIseD0tMjAseGVuZD1PT0wpLA0KICAgICAgICAgICAgICAgbGluZXR5cGU9ImRhc2hlZCIsYWxwaGE9MC43NSkrDQogIGdlb21fZXJyb3JiYXIoZGF0YT0uJT4lZmlsdGVyKCEhZm9zc2lsLnNwZWNpbWVucyxnZW51cz09IkR1bmtsZW9zdGV1cyIpLA0KICAgICAgICAgICAgICAgIGFlcyh5bWluPS5sb3dlcix5bWF4PS51cHBlciksd2lkdGg9MykrDQogIGdlb21fbGFiZWwoZGF0YT0uJT4lZmlsdGVyKCEhZm9zc2lsLnNwZWNpbWVucyxnZW51cz09IkR1bmtsZW9zdGV1cyIpLA0KICAgICAgICAgICAgIGFlcyh5PS51cHBlcix4PTAsbGFiZWw9cm91bmQoLnVwcGVyLDEpKSxzaXplPTMpKw0KICBnZW9tX2xhYmVsKGRhdGE9LiU+JWZpbHRlcighIWZvc3NpbC5zcGVjaW1lbnMsZ2VudXM9PSJEdW5rbGVvc3RldXMiKSwNCiAgICAgICAgICAgICBhZXMoeT0ubG93ZXIseD0wLGxhYmVsPXJvdW5kKC5sb3dlciwxKSksc2l6ZT0zKSsNCiAgZ2VvbV90ZXh0KGRhdGE9LiU+JWZpbHRlcighIWZvc3NpbC5zcGVjaW1lbnMsZ2VudXM9PSJEdW5rbGVvc3RldXMiKSwNCiAgICAgICAgICAgIGFlcyhsYWJlbD1zcGVjaW1lbix4PU9PTCwNCiAgICAgICAgICAgICAgICB5PWlmZWxzZShzcGVjaW1lbj09IkNNTkggNjA5MCIsLmxvd2VyLTEwLC5sb3dlcikpLGhqdXN0PTAsbnVkZ2VfeT0tMTMpKw0KICB0aGVtZV9jbGFzc2ljKCkNCmBgYA0KDQojIyBQcmVkaWN0aW5nIHVzaW5nIHBlbGFnaWMgdGF4YQ0KDQojIyMgTW9kZWwgb25seSBjb25zaWRlcmluZyBwZWxhZ2ljIHRheGENCg0KYGBge3J9DQpmaXQucGVsYWdpYzwtbG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCksDQogICAgICAgICAgICAgICAgZGF0YV9maW5hbCU+JQ0KICAgICAgICAgICAgICAgICBmaWx0ZXIoIXNoYXBlICVpbiUgYygibWFjcnVyaWZvcm0iLCJhbmd1aWxsaWZvcm0iKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIGhhYml0YXQgJWluJSBjKCJwZWxhZ2ljIiksDQogICAgICAgICAgICAgICAgICAgICAgICAhb3JkZXIgJWluJSBjKCJSYWppZm9ybWVzIiwiQ2hpbWFlcmlmb3JtZXMiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIGZhbWlseSAhPSJNb2xpZGFlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICFnZW51cyAlaW4lIGMoIkFsb3BpYXMiLCJSZWdhbGVjdXMiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIGxlbmd0aF9hcyE9ImVzdGltYXRlZCB0LmwuIikpDQoNCnN1bW1hcnkoZml0LnBlbGFnaWMpDQpyZWdyZXNzaW9uLnN0YXRzKGZpdC5wZWxhZ2ljKQ0KYGBgDQoNCiMjIyBNb2RlbCB1c2luZyBoYWJpdGF0IGFzIGFuIGFkZGl0aW9uYWwgY2F0ZWdvcmljYWwgdmFyaWFibGUNCg0KKipOb3RlOioqICpSZWdhbGVjdXMqIGFuZCAqQWxvcGlhcyogd2VyZSBleGNsdWRlZCBmcm9tIHRoaXMgbW9kZWwgZ2l2ZW4gdGhlaXIgaGlnaGx5IHVudXN1YWwgYm9keSBzaGFwZXMgY29tcGFyZWQgdG8gdHlwaWNhbCBwZWxhZ2ljIGZpc2hlcy4NCg0KYGBge3J9DQpmaXQucGVsYWdpY19jYXRlZ29yaWNhbDwtbG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCkraGFiaXRhdCsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChmYW1pbHk9PSJTZXJyYW5pZGFlIiksDQogICAgICAgICAgICAgICAgZGF0YV9maW5hbCU+JQ0KICAgICAgICAgICAgICAgICBmaWx0ZXIoIXNoYXBlICVpbiUgYygibWFjcnVyaWZvcm0iLCJhbmd1aWxsaWZvcm0iKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICFvcmRlciAlaW4lIGMoIlJhamlmb3JtZXMiLCJDaGltYWVyaWZvcm1lcyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIWdlbnVzICVpbiUgYygiQWxvcGlhcyIsIlJlZ2FsZWN1cyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoX2FzIT0iZXN0aW1hdGVkIHQubC4iKSkNCnN1bW1hcnkoZml0LnBlbGFnaWNfY2F0ZWdvcmljYWwpDQpyZWdyZXNzaW9uLnN0YXRzKGZpdC5wZWxhZ2ljX2NhdGVnb3JpY2FsKQ0KYGBgDQoNCmBgYHtyfQ0KZml0LnBlbGFnaWNfY2F0ZWdvcmljYWxfc3BlY2llc19hdmVyYWdlczwtDQogIGxtKGxvZyh0b3RhbF9sZW5ndGgpfmxvZyhPT0wpK2hhYml0YXQrKGZhbWlseT09IlNlcnJhbmlkYWUiKSwNCiAgICAgICAgICAgICAgICBkYXRhX3NwZWNpZXMgJT4lDQogICAgICAgICAgICAgICAgIGZpbHRlcighc2hhcGUgJWluJSBjKCJtYWNydXJpZm9ybSIsImFuZ3VpbGxpZm9ybSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIWdlbnVzICVpbiUgYygiQWxvcGlhcyIsIlJlZ2FsZWN1cyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIW9yZGVyICVpbiUgYygiUmFqaWZvcm1lcyIsIkNoaW1hZXJpZm9ybWVzIikpKQ0Kc3VtbWFyeShmaXQucGVsYWdpY19jYXRlZ29yaWNhbF9zcGVjaWVzX2F2ZXJhZ2VzKQ0KcmVncmVzc2lvbi5zdGF0cyhmaXQucGVsYWdpY19jYXRlZ29yaWNhbF9zcGVjaWVzX2F2ZXJhZ2VzKQ0KYGBgDQoNCiMjIyBQcmVkaWN0aW9ucyBmb3IgKkR1bmtsZW9zdGV1cyoNCg0KYGBge3J9DQpmaXQucGVsYWdpY19hdmVyYWdlczwtbG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCksZGF0YV9zcGVjaWVzJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihoYWJpdGF0PT0icGVsYWdpYyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAhc2hhcGUgJWluJSBjKCJtYWNydXJpZm9ybSIsImFuZ3VpbGxpZm9ybSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIWdlbnVzICVpbiUgYygiQWxvcGlhcyIsIlJlZ2FsZWN1cyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIWZhbWlseSAlaW4lIGMoIk1vbGlkYWUiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFvcmRlciAlaW4lIGMoIlJhamlmb3JtZXMiLCJDaGltYWVyaWZvcm1lcyIpKSkNCg0KZm9zc2lsX3RheGElPiUNCiAgZmlsdGVyKCEhZm9zc2lsLnNwZWNpbWVucyklPiVkcm9wX25hKGhhYml0YXQpJT4lDQogIGF1Z21lbnQoZml0LnBlbGFnaWMsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0IiklPiUNCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0LnBlbGFnaWMpJENGKSklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCJmaXQxIiwuKSkpJT4lDQogIGF1Z21lbnQoZml0LnBlbGFnaWNfYXZlcmFnZXMsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0IiklPiUNCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0LnBlbGFnaWNfYXZlcmFnZXMpJENGKSklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCJmaXQyIiwuKSkpJT4lDQogIGF1Z21lbnQoZml0LnBlbGFnaWNfY2F0ZWdvcmljYWwsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0IiklPiUNCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0LnBlbGFnaWNfY2F0ZWdvcmljYWwpJENGKSklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCJmaXQzIiwuKSkpJT4lDQogIGF1Z21lbnQoZml0LnBlbGFnaWNfY2F0ZWdvcmljYWxfc3BlY2llc19hdmVyYWdlcyxuZXdkYXRhPS4saW50ZXJ2YWw9InByZWRpY3QiKSU+JQ0KICBtdXRhdGUoYWNyb3NzKC5maXR0ZWQ6LnVwcGVyLH5leHAoLikqcmVncmVzc2lvbi5zdGF0cyhmaXQucGVsYWdpY19jYXRlZ29yaWNhbF9zcGVjaWVzX2F2ZXJhZ2VzKSRDRikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgiZml0NCIsLikpKSU+JQ0KICBtdXRhdGUoZml0MS5QRT1wYXN0ZTAoIigiLHJvdW5kKGZpdDEuZml0dGVkKigxLQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoZml0LnBlbGFnaWMpJGFkalBFLzEwMCksMSksDQogICAgICAgICAgICAgICAgICAgICLigJMiLHJvdW5kKGZpdDEuZml0dGVkKigxKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoZml0LnBlbGFnaWMpJGFkalBFLzEwMCksMSksIikiKSwNCiAgICAgICAgIGZpdDIuUEU9cGFzdGUwKCIoIixyb3VuZChmaXQyLmZpdHRlZCooMS0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKGZpdC5wZWxhZ2ljX2F2ZXJhZ2VzKSRhZGpQRS8xMDApLDEpLA0KICAgICAgICAgICAgICAgICAgICAi4oCTIixyb3VuZChmaXQyLmZpdHRlZCooMSsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKGZpdC5wZWxhZ2ljX2F2ZXJhZ2VzKSRhZGpQRS8xMDApLDEpLCIpIiksDQogICAgICAgICBmaXQzLlBFPXBhc3RlMCgiKCIscm91bmQoZml0MS5maXR0ZWQqKDEtDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQucGVsYWdpY19jYXRlZ29yaWNhbCkkYWRqUEUvMTAwKSwxKSwNCiAgICAgICAgICAgICAgICAgICAgIuKAkyIscm91bmQoZml0MS5maXR0ZWQqKDErDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQucGVsYWdpY19jYXRlZ29yaWNhbCkkYWRqUEUvMTAwKSwxKSwiKSIpLA0KICAgICAgICAgZml0NC5QRT1wYXN0ZTAoIigiLHJvdW5kKGZpdDQuZml0dGVkKigxLQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoZml0LnBlbGFnaWNfY2F0ZWdvcmljYWxfc3BlY2llc19hdmVyYWdlcykkYWRqUEUvMTAwKSwxKSwNCiAgICAgICAgICAgICAgICAgICAgIuKAkyIscm91bmQoZml0NC5maXR0ZWQqKDErDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQucGVsYWdpY19jYXRlZ29yaWNhbF9zcGVjaWVzX2F2ZXJhZ2VzKSRhZGpQRS8xMDApLDEpLCIpIiksDQogICAgICAgICBmaXQxLnJhbmdlPXBhc3RlMCgiKCIscm91bmQoZml0MS5sb3dlciwxKSwi4oCTIixyb3VuZChmaXQxLnVwcGVyLDEpLCIpIiksDQogICAgICAgICBmaXQyLnJhbmdlPXBhc3RlMCgiKCIscm91bmQoZml0Mi5sb3dlciwxKSwi4oCTIixyb3VuZChmaXQyLnVwcGVyLDEpLCIpIiksDQogICAgICAgICBmaXQzLnJhbmdlPXBhc3RlMCgiKCIscm91bmQoZml0My5sb3dlciwxKSwi4oCTIixyb3VuZChmaXQzLnVwcGVyLDEpLCIpIiksDQogICAgICAgICBmaXQ0LnJhbmdlPXBhc3RlMCgiKCIscm91bmQoZml0NC5sb3dlciwxKSwi4oCTIixyb3VuZChmaXQ0LnVwcGVyLDEpLCIpIikpJT4lDQogIHNlbGVjdCh0YXhvbixzcGVjaW1lbix0b3RhbF9sZW5ndGgsDQogICAgICAgICBmaXQxLmZpdHRlZCxmaXQxLlBFLGZpdDEucmFuZ2UsDQogICAgICAgICBmaXQyLmZpdHRlZCxmaXQyLlBFLGZpdDIucmFuZ2UsDQogICAgICAgICBmaXQzLmZpdHRlZCxmaXQzLlBFLGZpdDMucmFuZ2UsDQogICAgICAgICBmaXQ0LmZpdHRlZCxmaXQ0LlBFLGZpdDQucmFuZ2UsKSU+JQ0KICBrYWJsZShkaWdpdHM9MSwNCiAgICAgICAgY2FwdGlvbj0gIkxlbmd0aCBlc3RpbWF0ZXMgZm9yIHNwZWNpbWVucyBvZiA8aT5EdW5rbGVvc3RldXMgdGVycmVsbGk8L2k+IHVzaW5nIGEgbW9kZWwgY29uc2lkZXJpbmcgb25seSBwZWxhZ2ljIHRheGEsIGV4Y2x1ZGluZyByYXlzLCBtb2xpZHMsIGFuZCBhbmd1aWxsaWZvcm0gKGUuZy4sIDxpPlJlZ2FsZWN1czwvaT4gYW5kIG1hY3J1cmlmb3JtIChlLmcuLCA8aT5BbG9waWFzPC9pPikgdGF4YSIsDQogICAgICAgIGNvbC5uYW1lcyA9IGMoIlRheG9uIiwiU3BlY2ltZW4iLCJUb3RhbCBMZW5ndGgiLA0KICAgICAgICAgICAgICAgICAgICAgICJFc3QuIiwiKy8tICVQRSIsIjk1JSBQLkkuIiwNCiAgICAgICAgICAgICAgICAgICAgICAiRXN0LiIsIisvLSAlUEUiLCI5NSUgUC5JLiIsDQogICAgICAgICAgICAgICAgICAgICAgIkVzdC4iLCIrLy0gJVBFIiwiOTUlIFAuSS4iLA0KICAgICAgICAgICAgICAgICAgICAgICJFc3QuIiwiKy8tICVQRSIsIjk1JSBQLkkuIikpJT4lDQogIGNvbHVtbl9zcGVjKDEsIGl0YWxpYyA9IFQpJT4lDQogIGNvbHVtbl9zcGVjKGMoNCw3LDEwLDEzKSwgYm9sZCA9IFQpJT4lDQogIGFkZF9oZWFkZXJfYWJvdmUoYygiICI9MywiSW5kaXZpZHVhbCBTcGVjaW1lbnMiPTMsIlNwZWNpZXMgQXZlcmFnZXMiPTMsDQogICAgICAgICAgICAgICAgICAgICAiSW5kaXZpZHVhbCBTcGVjaW1lbnMiPTMsIlNwZWNpZXMgQXZlcmFnZXMiPTMpKSU+JQ0KICBhZGRfaGVhZGVyX2Fib3ZlKGMoIiAiPTMsIkNvbnNpZGVyaW5nIFBlbGFnaWMgVGF4YSBPbmx5Ij02LA0KICAgICAgICAgICAgICAgICAgICAgIlRyZWF0aW5nIExpZmUgSGFiaXRzIGFzIEFkZGl0aW9uYWwgQ2F0ZWdvcmljYWwgVmFyaWFibGUiPTYpKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCklPiUNCiAgc2Nyb2xsX2JveCh3aWR0aCA9ICIxMDAlIikNCmBgYA0KDQojIyMgUHJlZGljdGluZyB1c2luZyBwZWxhZ2ljIGFuZCBuZXJpdGljIHRheGENCg0KYGBge3J9DQpmaXQucGVsYWdpY19uZXJpdGljPC1sbShsb2codG90YWxfbGVuZ3RoKX5sb2coT09MKSwNCiAgICAgICAgICAgICAgICBkYXRhX2ZpbmFsJT4lDQogICAgICAgICAgICAgICAgIGZpbHRlcihoYWJpdGF0ICVpbiUgYygicGVsYWdpYyIsIm5lcml0aWMiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICFzaGFwZSAlaW4lIGMoImFuZ3VpbGxpZm9ybSIsIm1hY3J1cmlmb3JtIiksDQogICAgICAgICAgICAgICAgICAgICAgICAhb3JkZXIgJWluJSBjKCJSYWppZm9ybWVzIiwiQ2hpbWFlcmlmb3JtZXMiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIGZhbWlseSAhPSAiTW9saWRhZSIsDQogICAgICAgICAgICAgICAgICAgICAgICBnZW51cyE9IkFsb3BpYXMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoX2FzIT0iZXN0aW1hdGVkIHQubC4iKSkNCg0KZml0LnBlbGFnaWNfbmVyaXRpY19hdmVyYWdlczwtbG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCksDQogICAgICAgICAgICAgICAgZGF0YV9zcGVjaWVzJT4lDQogICAgICAgICAgICAgICAgIGZpbHRlcihoYWJpdGF0ICVpbiUgYygicGVsYWdpYyIsIm5lcml0aWMiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICFzaGFwZSAlaW4lIGMoImFuZ3VpbGxpZm9ybSIsIm1hY3J1cmlmb3JtIiksDQogICAgICAgICAgICAgICAgICAgICAgICAhb3JkZXIgJWluJSBjKCJSYWppZm9ybWVzIiwiQ2hpbWFlcmlmb3JtZXMiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIGZhbWlseSAhPSAiTW9saWRhZSIsDQogICAgICAgICAgICAgICAgICAgICAgICBnZW51cyE9IkFsb3BpYXMiKSkNCg0Kc3VtbWFyeShmaXQucGVsYWdpY19uZXJpdGljKQ0KcmVncmVzc2lvbi5zdGF0cyhmaXQucGVsYWdpY19uZXJpdGljKQ0KDQpmb3NzaWxfdGF4YSU+JQ0KICBmaWx0ZXIoISFmb3NzaWwuc3BlY2ltZW5zKSU+JQ0KICBhdWdtZW50KGZpdC5wZWxhZ2ljX25lcml0aWNfYXZlcmFnZXMsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0IiklPiUNCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0LnBlbGFnaWNfbmVyaXRpYykkQ0YpKSU+JQ0KICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnLicpKSwgZnVucyhwYXN0ZTAoImZpdDEiLC4pKSklPiUNCiAgYXVnbWVudChmaXQucGVsYWdpY19uZXJpdGljX2F2ZXJhZ2VzLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdCIpJT4lDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5wZWxhZ2ljX25lcml0aWNfYXZlcmFnZXMpJENGKSklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCJmaXQyIiwuKSkpJT4lDQogIG11dGF0ZShmaXQxLnJhbmdlPXBhc3RlMCgiKCIscm91bmQoZml0MS5sb3dlciwxKSwi4oCTIixyb3VuZChmaXQxLnVwcGVyLDEpLCIpIiksDQogICAgICAgICBmaXQyLnJhbmdlPXBhc3RlMCgiKCIscm91bmQoZml0Mi5sb3dlciwxKSwi4oCTIixyb3VuZChmaXQyLnVwcGVyLDEpLCIpIiksDQogICAgICAgICBmaXQxLlBFPXBhc3RlMCgiKCIscm91bmQoZml0MS5maXR0ZWQqKDEtDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQucGVsYWdpY19uZXJpdGljKSRhZGpQRS8xMDApLDEpLA0KICAgICAgICAgICAgICAgICAgICAi4oCTIixyb3VuZChmaXQxLmZpdHRlZCooMSsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKGZpdC5wZWxhZ2ljX25lcml0aWMpJGFkalBFLzEwMCksMSksIikiKSwNCiAgICAgICAgIGZpdDIuUEU9cGFzdGUwKCIoIixyb3VuZChmaXQyLmZpdHRlZCooMS0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKGZpdC5wZWxhZ2ljX25lcml0aWNfYXZlcmFnZXMpJGFkalBFLzEwMCksMSksDQogICAgICAgICAgICAgICAgICAgICLigJMiLHJvdW5kKGZpdDIuZml0dGVkKigxKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoZml0LnBlbGFnaWNfbmVyaXRpY19hdmVyYWdlcykkYWRqUEUvMTAwKSwxKSwiKSIpKSU+JQ0KICBzZWxlY3QodGF4b24sc3BlY2ltZW4sZml0MS5maXR0ZWQsZml0MS5QRSxmaXQxLnJhbmdlLGZpdDIuZml0dGVkLGZpdDIuUEUsZml0Mi5yYW5nZSklPiUNCiAga2FibGUoZGlnaXRzPTEsDQogICAgICAgIGNhcHRpb249ICJMZW5ndGggZXN0aW1hdGVzIGZvciBzcGVjaW1lbnMgb2YgPGk+RHVua2xlb3N0ZXVzIHRlcnJlbGxpPC9pPiB1c2luZyBhIG1vZGVsIGNvbnNpZGVyaW5nIHBlbGFnaWMgYW5kIG5lcml0aWMgdGF4YSwgZXhjbHVkaW5nIHJheXMsIG1vbGlkcywgYW5kIGFuZ3VpbGxpZm9ybSAoZS5nLiwgPGk+UmVnYWxlY3VzPC9pPiBhbmQgbWFjcnVyaWZvcm0gKGUuZy4sIDxpPkFsb3BpYXM8L2k+KSB0YXhhIiwNCiAgICAgICAgY29sLm5hbWVzID0gYygiVGF4b24iLCJTcGVjaW1lbiIsDQogICAgICAgICAgICAgICAgICAgICAgIkVzdC4iLCIrLy0gUEUiLCI5NSUgUC5JLiIsDQogICAgICAgICAgICAgICAgICAgICAgIkVzdC4iLCIrLy0gUEUiLCI5NSUgUC5JLiIpKSU+JQ0KICBhZGRfaGVhZGVyX2Fib3ZlKGMoIiAiPTIsIkluZGl2aWR1YWwgU3BlY2ltZW5zIj0zLCJTcGVjaWVzIEF2ZXJhZ2VzIj0zKSklPiUNCiAgY29sdW1uX3NwZWMoMSwgaXRhbGljID0gVCklPiUNCiAgY29sdW1uX3NwZWMoYygzLDYpLCBib2xkID0gVCklPiUNCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KIyMgUHJlZGljdGluZyB1c2luZyBzaGFya3Mgb25seQ0KDQpOb3RlOiB0aGlzIG1vZGVsIGNhbGN1bGF0ZWQgZXhjbHVkaW5nIFJhamlmb3JtZXMsIENoaW1hZXJpZm9ybWVzLCBhbmQgKkFsb3BpYXMqDQoNCmBgYHtyLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KZml0LnNoYXJrczwtbG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCksDQogICAgICAgICAgICAgICAgZGF0YV9maW5hbCU+JQ0KICAgICAgICAgICAgICAgICBmaWx0ZXIoY2xhZGU9PSJDaG9uZHJpY2h0aHllcyIsDQogICAgICAgICAgICAgICAgICAgICAgICAhKG9yZGVyICVpbiUgYygiUmFqaWZvcm1lcyIsIkNoaW1hZXJpZm9ybWVzIikpLA0KICAgICAgICAgICAgICAgICAgICAgICAgZ2VudXMhPSJBbG9waWFzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgIGxlbmd0aF9hcyE9ImVzdGltYXRlZCB0LmwuIikpDQoNCnN1bW1hcnkoZml0LnNoYXJrcykNCnJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXJrcykNCg0KcHJlZGljdC5zaGFyazwtKHByZWRpY3QoZml0LnNoYXJrcywNCiAgICAgICAgICAgZm9zc2lsX3RheGElPiVmaWx0ZXIoIWlzLm5hKE9PTCkpLA0KICAgICAgICAgICBpbnRlcnZhbD0icHJlZGljdGlvbiIpJT4lDQogIGV4cCgpKnJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXJrcykkQ0YpJT4lDQogIGRhdGEuZnJhbWUoKSU+JXJvd25hbWVzX3RvX2NvbHVtbigpJT4lDQogIGFycmFuZ2UoZml0KSU+JQ0KICBsZWZ0X2pvaW4oZm9zc2lsX3RheGElPiVyb3duYW1lc190b19jb2x1bW4oKSxieT0icm93bmFtZSIpJT4lDQogIGZpbHRlcighIWZvc3NpbC5zcGVjaW1lbnMpJT4lDQogIHNlbGVjdCh0YXhvbixzcGVjaW1lbix0b3RhbF9sZW5ndGgsZml0LGx3cix1cHIpDQpwcmVkaWN0LnNoYXJrJT4lDQogIGthYmxlKGRpZ2l0cz0xLA0KICAgICAgICBjYXB0aW9uPSAiTGVuZ3RoIGVzdGltYXRlcyBmb3Igc3BlY2ltZW5zIG9mIDxpPkR1bmtsZW9zdGV1cyB0ZXJyZWxsaTwvaT4gYW5kIG90aGVyIGFydGhyb2RpcmVzIHVzaW5nIGEgbW9kZWwgY29uc2lkZXJpbmcgb25seSBjaG9uZHJpY2h0aHlhbnMsIGV4Y2x1ZGluZyByYXlzLCBjaGltZXJhcywgYW5kIDxpPkFsb3BpYXM8L2k+IiwNCiAgICAgICAgY29sLm5hbWVzID0gYygiVGF4b24iLCJTcGVjaW1lbiIsIkFjdHVhbCBMZW5ndGgiLCJFc3QuIiwiTHdyIDk1JSBQLkkuIiwiVXByIDk1JSBQLkkuIikpJT4lDQogIGNvbHVtbl9zcGVjKDEsIGl0YWxpYyA9IFQpJT4lDQogIGNvbHVtbl9zcGVjKDQsIGJvbGQgPSBUKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQojIyMgTW9kZWwgZXhjbHVkaW5nIExhbW5pZm9ybWVzIGFuZCBFY2hpbm9yaGluaWZvcm1lcw0KDQpCZWNhdXNlIG1hbnkgb2YgdGhlIHZlcnkgbGFyZ2VzdCBmaXNoZXMgaW4gdGhpcyBhbmFseXNpcyBhcmUgTGFtbmlmb3JtZXMgb3IgRWNoaW5vcmhpbmlmb3JtZXMuDQpMYW1uaWZvcm1lcyB0ZW5kIHRvIHNob3cgYSBkaXN0aW5jdGx5IGxvbmdlciBPT0wgcmVsYXRpdmUgdG8gdGhlaXIgdG90YWwgbGVuZ3RoICh3aGV0aGVyIHRoaXMgaXMgZHVlIHRvIHRoZWlyIGFudGVyaW9ycG9zdGVyaW9ybHkgc2hvcnQgdHJ1bmssIGFuIGVsb25nYXRlZCBnaWxsIHJlZ2lvbiwgb3IgYm90aCBpcyB1bmNsZWFyIGF0IHRoaXMgdGltZSkuIE9uIHRoZSBvdGhlciBoYW5kLCAqRWNoaW5vcmhpbnVzKiBzaG93cyBhbiB1bnVzdWFsbHkgbG9uZyBoZWFkIGFtb25nIGZpc2hlcyBhbmQgYXBwZWFycyB0byBkaXNwbGF5IGEgdHJ1bmNhdGVkIGFiZG9tZW4gKGkuZS4sIHBvc3RwZWx2aWMgcmVnaW9uIG9mIHRoZSBib2R5KS4gW1Zhcm9qZWFuICgxOTcyKV0oI3JlZmVyZW5jZXMpIG5vdGVzIHRoYXQgKkUuIGNvb2tlaSogZGlzcGxheXMgYW4gdW51c3VhbCBwb3NpdGl2ZSBhbGxvbWV0cnkgb2YgdGhlIGhlYWQgYW5kIGFiZG9tZW4gcmVsYXRpdmUgdG8gdGhlIHRydW5rLCBhbmQgaXQgaXMgcG9zc2libGUgdGhpcyBpcyBpbmRpY2F0aXZlIG9mIHNvbWUgdW51c3VhbCBkZXZlbG9wbWVudGFsIHBhdHRlcm4gcmVzdWx0aW5nIGluIHRoZSBzdHJhbmdlIGJvZHkgcHJvcG9ydGlvbnMgb2YgdGhpcyB0YXhvbi4gQmVjYXVzZSBvZiB0aGVzZSBmYWN0b3JzLCBpdCBpcyB3b3J0aCBxdWVzdGlvbmluZyB3aGV0aGVyIHRoZSByZWdyZXNzaW9uIGxpbmUgbWlnaHQgYmUgYmlhc2VkIGluIGZhdm9yIG9mIExhbW5pZm9ybWVzIGFuZCBFY2hpbm9yaGluZm9ybWVzLCBhbmQgdGh1cyBwcm9kdWNpbmcgc2hvcnRlciBib2R5IGxlbmd0aCBlc3RpbWF0ZXMgaW4gKkR1bmtsZW9zdGV1cyouDQoNCmBgYHtyLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KZml0LmxhbW5pZm9ybWVzPC1sbShsb2codG90YWxfbGVuZ3RoKX5sb2coT09MKSwNCiAgICAgICAgICAgICAgICBkYXRhX2ZpbmFsJT4lDQogICAgICAgICAgICAgICAgIGZpbHRlcighb3JkZXIgJWluJSBjKCJMYW1uaWZvcm1lcyIsIkVjaGlub3JoaW5pZm9ybWVzIiksDQogICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGhfYXMhPSJlc3RpbWF0ZWQgdC5sLiIpKQ0KZml0LmxhbW5pZm9ybWVzMjwtbG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCksDQogICAgICAgICAgICAgICAgZGF0YV9zcGVjaWVzJT4lDQogICAgICAgICAgICAgICAgIGZpbHRlcighb3JkZXIgJWluJSBjKCJMYW1uaWZvcm1lcyIsIkVjaGlub3JoaW5pZm9ybWVzIikpKQ0KZml0LmxhbW5pZm9ybWVzMzwtbG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCkrc2hhcGUsDQogICAgICAgICAgICAgICAgZGF0YV9maW5hbCU+JQ0KICAgICAgICAgICAgICAgICBmaWx0ZXIoIW9yZGVyICVpbiUgYygiTGFtbmlmb3JtZXMiLCJFY2hpbm9yaGluaWZvcm1lcyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoX2FzIT0iZXN0aW1hdGVkIHQubC4iKSkNCg0KcHJlZGljdC5sYW1uaWZvcm1lczwtKGZvc3NpbF90YXhhJT4lDQogICAgICAgICAgICAgICAgICAgICAgICBkcm9wX25hKE9PTCklPiUNCiAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcighIWZvc3NpbC5zcGVjaW1lbnMpJT4lDQogIG11dGF0ZShmaXQ9cHJlZGljdChmaXQubGFtbmlmb3JtZXMsLixpbnRlcnZhbD0icHJlZGljdGlvbiIpWywxXSwNCiAgICAgICAgIGx3cj1wcmVkaWN0KGZpdC5sYW1uaWZvcm1lcywuLGludGVydmFsPSJwcmVkaWN0aW9uIilbLDJdLA0KICAgICAgICAgdXByPXByZWRpY3QoZml0LmxhbW5pZm9ybWVzLC4saW50ZXJ2YWw9InByZWRpY3Rpb24iKVssM10sDQogICAgICAgICBmaXQyPXByZWRpY3QoZml0LmxhbW5pZm9ybWVzMiwuLGludGVydmFsPSJwcmVkaWN0aW9uIilbLDFdLA0KICAgICAgICAgbHdyMj1wcmVkaWN0KGZpdC5sYW1uaWZvcm1lczIsLixpbnRlcnZhbD0icHJlZGljdGlvbiIpWywyXSwNCiAgICAgICAgIHVwcjI9cHJlZGljdChmaXQubGFtbmlmb3JtZXMyLC4saW50ZXJ2YWw9InByZWRpY3Rpb24iKVssM10sDQogICAgICAgICBmaXQzPXByZWRpY3QoZml0LmxhbW5pZm9ybWVzMywuLGludGVydmFsPSJwcmVkaWN0aW9uIilbLDFdLA0KICAgICAgICAgbHdyMz1wcmVkaWN0KGZpdC5sYW1uaWZvcm1lczMsLixpbnRlcnZhbD0icHJlZGljdGlvbiIpWywyXSwNCiAgICAgICAgIHVwcjM9cHJlZGljdChmaXQubGFtbmlmb3JtZXMzLC4saW50ZXJ2YWw9InByZWRpY3Rpb24iKVssM10sDQogICAgICAgICBhY3Jvc3MoZml0OnVwcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0LmxhbW5pZm9ybWVzKSRDRiksDQogICAgICAgICBhY3Jvc3MoZml0Mjp1cHIyLH5leHAoLikqcmVncmVzc2lvbi5zdGF0cyhmaXQubGFtbmlmb3JtZXMyKSRDRiksDQogICAgICAgICBhY3Jvc3MoZml0Mzp1cHIzLH5leHAoLikqcmVncmVzc2lvbi5zdGF0cyhmaXQubGFtbmlmb3JtZXMzKSRDRikpJT4lDQogIGFycmFuZ2UoZml0KSU+JQ0KICBmaWx0ZXIoZ2VudXMgJWluJSBjKCJDb2Njb3N0ZXVzIiwiRHVua2xlb3N0ZXVzIikpJT4lDQogIHNlbGVjdCh0YXhvbixzcGVjaW1lbixmaXQsbHdyLHVwcixmaXQyLGx3cjIsdXByMixmaXQzLGx3cjMsdXByMykpDQpwcmVkaWN0LmxhbW5pZm9ybWVzJT4lDQogIG11dGF0ZShyYW5nZT1wYXN0ZTAoIigiLHJvdW5kKGx3ciwxKSwiLSIscm91bmQodXByLDEpLCIpIiksDQogICAgICAgICByYW5nZTI9cGFzdGUwKCIoIixyb3VuZChsd3IyLDEpLCItIixyb3VuZCh1cHIyLDEpLCIpIiksDQogICAgICAgICByYW5nZTM9cGFzdGUwKCIoIixyb3VuZChsd3IzLDEpLCItIixyb3VuZCh1cHIzLDEpLCIpIikpJT4lDQogIHJvd25hbWVzX3RvX2NvbHVtbigpJT4lDQogIHNlbGVjdCh0YXhvbixzcGVjaW1lbixmaXQscmFuZ2UsZml0MixyYW5nZTIsZml0MyxyYW5nZTMpJT4lDQogIGthYmxlKGRpZ2l0cz0xLA0KICAgICAgICBjYXB0aW9uPSAiTGVuZ3RoIGVzdGltYXRlcyBmb3Igc3BlY2ltZW5zIG9mIDxpPkR1bmtsZW9zdGV1cyB0ZXJyZWxsaTwvaT4gdXNpbmcgYSBtb2RlbCBjb25zaWRlcmluZyBhbGwgZmlzaGVzIGV4Y2VwdCBMYW1uaWZvcm1lcy4gVGhpcyB0YWJsZSBmb2N1c2VzIG9uIHJlc3VsdHMgaW4gPGk+RHVua2xlb3N0ZXVzPC9pPiBhcyBMYW1uaWZvcm1lcyBhcmUgZXhwZWN0ZWQgdG8gYmlhcyBvbmx5IHRoZSB2ZXJ5IHVwcGVyIHBhcnRzIG9mIHRoZSByZWdyZXNzaW9uIG1vZGVsIiwNCiAgICAgICAgY29sLm5hbWVzID0gYygiVGF4b24iLCJTcGVjaW1lbiIsIkVzdC4iLCI5NSUgUC5JLiIsIkVzdC4iLCI5NSUgUC5JLiIsIkVzdC4iLCI5NSUgUC5JLiIpKSU+JQ0KICBhZGRfaGVhZGVyX2Fib3ZlKGMoIiAiPTIsDQogICAgICAgICAgICAgICAgICAgICAiSW5kaXZpZHVhbCBEYXRhIj0yLA0KICAgICAgICAgICAgICAgICAgICAgIlNwZWNpZXMgQXZlcmFnZXMiPTIsDQogICAgICAgICAgICAgICAgICAgICAiSW5kaXZpZHVhbCBEYXRhLCBXaXRoIFNoYXBlIj0yKSklPiUNCiAgY29sdW1uX3NwZWMoMSwgaXRhbGljID0gVCklPiUNCiAgY29sdW1uX3NwZWMoYygzLDUsNyksYm9sZD1UKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQpPbWl0dGluZyBMYW1uaWZvcm1lcyBhbmQgRWNoaW5vcmhpbm9mb3JtZXMgZnJvbSB0aGUgYW5hbHlzaXMgcmVzdWx0cyBpbiBhIHNsaWdodCBpbmZsYXRpb24gb2YgYm9keSBsZW5ndGggcmVsYXRpdmUgdG8gb3RoZXIgbW9kZWxzLiBIb3dldmVyIHRoaXMgaXMgbW9zdGx5IGluIHRoZSBpbmRpdmlkdWFsIHNwZWNpbWVuIG1vZGVsIHdpdGhvdXQgYW55IGFkZGl0aW9uYWwgYXNzdW1wdGlvbnMsIGFuZCBtb3N0bHkgZHVlIHRvIHRoZSBoZWF2eSBzYW1wbGluZyBvZiBsb25nLWJvZGllZCBJc3Rpb3Bob3JpZm9ybWVzIChlLmcuLCAqVGV0cmFwdHVydXMqKSBpbiB0aGlzIGRhdGEgc2V0LiBVc2luZyBzcGVjaWVzIGF2ZXJhZ2VzIG9yIHVzaW5nIGEgbW9kZWwgdGhhdCBhbHNvIGNvbnNpZGVycyBzaGFwZSBwcm9kdWNlcyByZXN1bHRzIG11Y2ggY2xvc2VyIHRvIHRob3NlIHdpdGhvdXQgTGFtbmlmb3JtZXMgb3IgRWNoaW5vcmhpbmlmb3JtZXMuDQoNCkFkZGl0aW9uYWxseSwgdGhlIHJlc3VsdHMgb2YgdGhlIG1vZGVsIGV4Y2x1ZGluZyBMYW1uaWZvcm1lcyBhcmUgbm90IGNvbnNpZGVyZWQgdmVyeSByZWxpYWJsZSBhcyB0aGV5IHJlc3VsdCBpbiB0aGUgZXhjbHVzaW9uIG9mIHRheGEgdGhhdCB3b3VsZCBiZSBleHBlY3RlZCB0byBiZSBtb3N0IHNpbWlsYXIgdG8gKkR1bmtsZW9zdGV1cyogaW4gZWNvbG9neSBhbmQgYm9keSBzaGFwZSAoW0NhcnIgMjAxMF0oI3JlZmVyZW5jZXMpLCBbRmVycsOzbiBldCBhbC4gMjAxN10oI3JlZmVyZW5jZXMpKS4gQWx0aG91Z2ggbGFtbmlkcyBhbmQgb3RoZXIgcGVsYWdpYyBsYW1uaWZvcm1lcyBzaG93IGEgZGlzdGluY3QgdW5kZXJlc3RpbWF0ZSBvZiBib2R5IGxlbmd0aCB1c2luZyBPT0wsICpEdW5rbGVvc3RldXMqIG1pZ2h0IGJlIGV4cGVjdGVkIHRvIGJlIG1vcmUgbGlrZWx5IHRvIHNob3cgYSBtb3JlIHNpbWlsYXIgcHJvcG9ydGlvbmFsIHJlbGF0aW9uc2hpcCB0byBsYW1uaWRzIHRoYW4gYW55IG90aGVyIHRheG9uIChpLmUuLCBoYXZpbmcgYSBzaG9ydGVyIHRydW5rIGFuZCBsb25nZXIgT09MIHJlbGF0aXZlIHRvIHRvdGFsIGxlbmd0aCB0aGFuIHRheGEgbGlrZSAqQ29jY29zdGV1cyopIGdpdmVuIGl0IGhhcyBhbHNvIGJlZW4gaW50ZXJwcmV0ZWQgYXMgYW4gYWN0aXZlIHBlbGFnaWMgc3dpbW1lciAoW0NhcnIgMjAxMF0oI3JlZmVyZW5jZXMpKS4NCg0KIyMgVXNpbmcgT09MIHRvIGVzdGltYXRlIHRvdGFsIGxlbmd0aCBtaW51cyBzbm91dCBsZW5ndGggYW5kIHRoZW4gYWRkaW5nIHNub3V0IGxlbmd0aCBvbiBhcyBhbiBpbnRlZ2VyDQoNCkJlY2F1c2UgYXJ0aHJvZGlyZXMgaGF2ZSBhIHByb3BvcnRpb25hbGx5IHNob3J0ZXIgc25vdXQgdGhhbiBtb3N0IG90aGVyIGZpc2hlcywgb25lIHF1ZXN0aW9uIGlzIHdoZXRoZXIgdGhpcyBjb3VsZCBoYXZlIGEgYmlhc2luZyBlZmZlY3Qgb24gdGhlIG1vZGVsIGJlY2F1c2UgaXQgYXNzdW1lcyB0aGF0IGFydGhyb2RpcmVzIGhhdmUgcm9zdHJhbCBwcm9wb3J0aW9ucyBzaW1pbGFyIHRvIG90aGVyIGZpc2hlcy4gVGh1cywgYSBtb2RlbCB3YXMgZml0dGVkIHNlZWluZyBob3cgd2VsbCBPT0wgcHJlZGljdGVkIHRvdGFsIGxlbmd0aCBtaW51cyBzbm91dCBsZW5ndGgsIGFuZCB0aGVuIGFkZGluZyBzbm91dCBsZW5ndGggYmFjayBvbiBhcyBhbiBpbnRlZ2VyIGJlY2F1c2UgdGhpcyB3YXMgYSBrbm93biwgbWVhc3VyYWJsZSB2YWx1ZSBpbiBzcGVjaW1lbnMgb2YgKkR1bmtsZW9zdGV1cyB0ZXJyZWxsaSouDQoNCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30NCmZpdC5taW51c19zbm91dF9sZW5ndGgxPC1sbShsb2codG90YWxfbGVuZ3RoLXNub3V0X2xlbmd0aCl+bG9nKE9PTCkrKGZhbWlseT09IlNlcnJhbmlkYWUiKSsoZmFtaWx5PT0iSG9sb2NlbnRyaWRhZSIpLGRhdGFfZmluYWwpDQpmaXQubWludXNfc25vdXRfbGVuZ3RoMjwtbG0obG9nKHRvdGFsX2xlbmd0aC1zbm91dF9sZW5ndGgpfmxvZyhPT0wpKyhmYW1pbHk9PSJTZXJyYW5pZGFlIikrKGZhbWlseT09IkhvbG9jZW50cmlkYWUiKSxkYXRhX3NwZWNpZXMpDQoNCnN1bW1hcnkoZml0Lm1pbnVzX3Nub3V0X2xlbmd0aDEpDQpyYmluZCgiSW5kaXZpZHVhbCBEYXRhIj1yZWdyZXNzaW9uLnN0YXRzKGZpdC5taW51c19zbm91dF9sZW5ndGgxKSwNCiAgICAgICJTcGVjaWVzIEF2ZXJhZ2VzIj1yZWdyZXNzaW9uLnN0YXRzKGZpdC5taW51c19zbm91dF9sZW5ndGgyKSkNCmBgYA0KDQpOb3RlIHRoYXQgdGhlIHN1bW1hcnkgc3RhdGlzdGljcyBwcmVzZW50ZWQgaGVyZSBhcmUgZm9yIHRvdGFsIGxlbmd0aCAtIHNub3V0IGxlbmd0aCwgbm90IHRvdGFsIGxlbmd0aC4gU25vdXQgbGVuZ3RoIG11c3QgdGhlbiBiZSBhZGRlZCBiYWNrIG9uIGFzIGFuIGludGVnZXIgdG8gZ2V0IHRoZSB0cnVlIHRvdGFsIGxlbmd0aC4NCg0KYGBge3J9DQpmb3NzaWxfdGF4YSU+JQ0KICBmaWx0ZXIoISFmb3NzaWwuc3BlY2ltZW5zLCFpcy5uYShzbm91dF9sZW5ndGgpKSU+JQ0KICBhdWdtZW50KGZpdC5taW51c19zbm91dF9sZW5ndGgxLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdGlvbiIpJT4lDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5taW51c19zbm91dF9sZW5ndGgxKSRDRisNCiAgICAgICAgICAgICAgICAgIHNub3V0X2xlbmd0aCksDQogICAgICAgICAuUEU9cGFzdGUwKCIoIixyb3VuZCguZml0dGVkKigxLQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoZml0Lm1pbnVzX3Nub3V0X2xlbmd0aDEpJGFkalBFLzEwMCksMSksDQogICAgICAgICAgICAgICAgICAgICLigJMiLHJvdW5kKC5maXR0ZWQqKDErDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQubWludXNfc25vdXRfbGVuZ3RoMSkkYWRqUEUvMTAwKSwxKSwiKSIpLA0KICAgICAgICAgLnJhbmdlPXBhc3RlMCgiKCIscm91bmQoLmxvd2VyLDEpLA0KICAgICAgICAgICAgICAgICAgICAgICAi4oCTIixyb3VuZCgudXBwZXIsMSksDQogICAgICAgICAgICAgICAgICAgICAgICIpIikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgiZml0MSIsLikpKSU+JQ0KICBhdWdtZW50KGZpdC5taW51c19zbm91dF9sZW5ndGgyLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdGlvbiIpJT4lDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5PT0wpJENGKw0KICAgICAgICAgICAgICAgICAgc25vdXRfbGVuZ3RoKSwNCiAgICAgICAgIC5QRT1wYXN0ZTAoIigiLHJvdW5kKC5maXR0ZWQqKDEtDQogICAgICAgICAgICAgICAgICAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKGZpdC5taW51c19zbm91dF9sZW5ndGgyKSRhZGpQRS8xMDApLDEpLA0KICAgICAgICAgICAgICAgICAgICAi4oCTIixyb3VuZCguZml0dGVkKigxKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQubWludXNfc25vdXRfbGVuZ3RoMikkYWRqUEUvMTAwKSwxKSwiKSIpLA0KICAgICAgICAgLnJhbmdlPXBhc3RlMCgiKCIscm91bmQoLmxvd2VyLDEpLA0KICAgICAgICAgICAgICAgICAgICAgICAi4oCTIixyb3VuZCgudXBwZXIsMSksDQogICAgICAgICAgICAgICAgICAgICAgICIpIikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgiZml0MiIsLikpKSU+JQ0KICBzZWxlY3QodGF4b24sc3BlY2ltZW4sdG90YWxfbGVuZ3RoLGZpdDEuZml0dGVkLGZpdDEuUEUsZml0MS5yYW5nZSxmaXQyLmZpdHRlZCxmaXQyLlBFLGZpdDIucmFuZ2UpJT4lDQogIGthYmxlKGRpZ2l0cz0xLGFsaWduPWMoImwiLCJjIiwiYyIsImMiLCJjIiwiYyIsImMiLCJjIiksDQogICAgICAgIGNhcHRpb249ICJMZW5ndGggZXN0aW1hdGVzIGZvciBzcGVjaW1lbnMgb2YgPGk+RHVua2xlb3N0ZXVzIHRlcnJlbGxpPC9pPiB1c2luZyBhIG1vZGVsIGVzdGltYXRpbmcgdG90YWwgbGVuZ3RoIG1pbnVzIHNub3V0IGxlbmd0aCB1c2luZyBPT0wgYW5kIHRyZWF0aW5nIHNub3V0IGxlbmd0aCBhcyBhIGtub3duIGludGVnZXIiLA0KICAgICAgICBjb2wubmFtZXM9YygiVGF4b24iLCJTcGVjaW1lbiIsIkFjdHVhbCBMZW5ndGgiLCJFc3QuIiwiKy8tICVQRSIsIjk1JSBQLkkuIiwNCiAgICAgICAgICAgICAgICAgICAgIkVzdC4iLCIrLy0gJVBFIiwiOTUlIFAuSS4iKSklPiUNCiAgYWRkX2hlYWRlcl9hYm92ZShjKCIgIj0zLCJJbmRpdmlkdWFsIERhdGEiPTMsIlNwZWNpZXMgQXZlcmFnZXMiPTMpKSU+JQ0KICBjb2x1bW5fc3BlYygxLCBpdGFsaWMgPSBUKSU+JQ0KICBjb2x1bW5fc3BlYyhjKDQsNyksIGJvbGQgPSBUKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQpgYGB7cn0NCmRhdGFfZmluYWwlPiUNCiAgbXV0YXRlKGZpdDE9ZXhwKHByZWRpY3QoZml0Lk9PTCwuKSkqDQogICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoZml0Lk9PTCkkQ0YsDQogICAgICAgICBmaXQyPWV4cChwcmVkaWN0KGZpdC5taW51c19zbm91dF9sZW5ndGgxLC4pKSoNCiAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQubWludXNfc25vdXRfbGVuZ3RoMSkkQ0YsDQogICAgICAgICBmaXQzPWV4cChwcmVkaWN0KGZpdC5taW51c19zbm91dF9sZW5ndGgyLC4pKSoNCiAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQubWludXNfc25vdXRfbGVuZ3RoMikkQ0YsDQogICAgICAgICBQRTE9KChmaXQxLXRvdGFsX2xlbmd0aCkvZml0MSkqMTAwLA0KICAgICAgICAgUEUyPSgoKGZpdDIrc25vdXRfbGVuZ3RoKS10b3RhbF9sZW5ndGgpLyhmaXQyK3Nub3V0X2xlbmd0aCkpKjEwMCwNCiAgICAgICAgIFBFMz0oKChmaXQzK3Nub3V0X2xlbmd0aCktdG90YWxfbGVuZ3RoKS8oZml0Mytzbm91dF9sZW5ndGgpKSoxMDApJT4lDQogIGRyb3BfbmEoUEUxLFBFMixQRTMpJT4lDQogIHN1bW1hcmlzZShOPW4oKSxQRTE9bWVhbihhYnMoUEUxKSksUEUyPW1lYW4oYWJzKFBFMikpLFBFMz1tZWFuKGFicyhQRTMpKSklPiUNCiAga2FibGUoZGlnaXRzPWMoMSwyLDIsMiksYWxpZ249YygibCIsImMiLCJjIiwiYyIpLA0KICAgICAgICBjb2wubmFtZXM9YygiTiIsIlVzaW5nIE9PTCB0byBlc3RpbWF0ZSB0b3RhbCBsZW5ndGgiLCJJbmRpdmlkdWFsIGRhdGEiLCJTcGVjaWVzIGF2ZXJhZ2VzIiksDQogICAgICAgIGNhcHRpb249IkNvbXBhcmlzb24gb2YgUEU8c3ViPmNmPC9zdWI+IGJldHdlZW4gbW9kZWxzIHRyZWF0aW5nIHNub3V0IGxlbmd0aCBhcyBhIHNlcGFyYXRlIGludGVnZXIgYW5kIHRob3NlIHVzaW5nIE9PTCB0byBlc3RpbWF0ZSB0b3RhbCBsZW5ndGggYnkgaXRzZWxmLiBUaGlzIHN0YXRpc3RpYyBoYWQgdG8gYmUgY2FsY3VsYXRlZCBzZXBhcmF0ZWx5IGJlY2F1c2UgdGhlIHJlZ3Jlc3Npb24gbW9kZWwgZm9yIHRoZSBmb3JtZXIgaXMgYmV0d2VlbiBPT0wgYW5kICd0b3RhbCBsZW5ndGggbWludXMgc25vdXQgbGVuZ3RoJywgbm90IHRvdGFsIGxlbmd0aC4iKSU+JQ0KICBhZGRfaGVhZGVyX2Fib3ZlKGMoIiAiPTIsIlRyZWF0aW5nIHNub3V0IGxlbmd0aCBhcyBhIHNlcGFyYXRlIGludGVnZXIiPTIpKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQojIyBQcmVkaWN0aW5nIHVzaW5nIGEgbW9kZWwgdGhhdCBpbmNsdWRlcyBzaGFwZSBhcyBhIGNvdmFyaWF0ZQ0KDQpgYGB7cn0NCmZpdC53aXRoX3NoYXBlPC1sbShsb2codG90YWxfbGVuZ3RoKX5sb2coT09MKStzaGFwZSsoZmFtaWx5PT0iU2VycmFuaWRhZSIpKyhmYW1pbHk9PSJIb2xvY2VudHJpZGFlIiksDQogICAgICAgICAgIGRhdGFfaW5pdGlhbCU+JWZpbHRlcighZmFtaWx5ICVpbiUgYygiQmFsaXN0aWRhZSIsIk1vbmFjYW50aGlkYWUiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFnZW51cyAlaW4lIGMoIlJlZ2FsZWN1cyIsIlJoaW5vY2hpbWFlcmEiKSklPiUNCiAgICAgICAgICAgICBkcm9wX25hKHNoYXBlKSkNCnN1bW1hcnkoZml0LndpdGhfc2hhcGUpDQpyZWdyZXNzaW9uLnN0YXRzKGZpdC53aXRoX3NoYXBlKQ0KDQpmb3NzaWxfdGF4YSU+JQ0KICBmaWx0ZXIoISFmb3NzaWwuc3BlY2ltZW5zKSU+JQ0KICBhdWdtZW50KGZpdC53aXRoX3NoYXBlLA0KICAgICAgICAgIG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdCIpJT4lDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC53aXRoX3NoYXBlKSRDRikpJT4lDQogIG11dGF0ZShyYW5nZTE9cGFzdGUwKCIoIixyb3VuZCgubG93ZXIsMSksIuKAkyIscm91bmQoLnVwcGVyLDEpLCIpIiksDQogICAgICAgICBQRTFfbG93ZXI9LmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQud2l0aF9zaGFwZSkkYWRqUEUpLzEwMCksDQogICAgICAgICBQRTFfbG93ZXI9LmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQud2l0aF9zaGFwZSkkYWRqUEUpLzEwMCksDQogICAgICAgICBQRTFfdXBwZXI9LmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQud2l0aF9zaGFwZSkkYWRqUEUpLzEwMCksDQogICAgICAgICBQRV9yYW5nZTE9cGFzdGUwKCIoIixyb3VuZChQRTFfbG93ZXIsMSksIuKAkyIscm91bmQoUEUxX3VwcGVyLDEpLCIpIikpJT4lDQogIHNlbGVjdCh0YXhvbixzcGVjaW1lbix0b3RhbF9sZW5ndGgsLmZpdHRlZCxQRV9yYW5nZTEscmFuZ2UxKSU+JQ0KICBrYWJsZShkaWdpdHM9MSwNCiAgICAgICAgYWxpZ249YygibCIscmVwKCJjIiw1KSksDQogICAgICAgIGNhcHRpb249ICJMZW5ndGggZXN0aW1hdGVzIGZvciBzcGVjaW1lbnMgb2YgPGk+RHVua2xlb3N0ZXVzIHRlcnJlbGxpPC9pPiBhbmQgb3RoZXIgYXJ0aHJvZGlyZXMgdXNpbmcgYSBtb2RlbCB0aGF0IGluY2x1ZGVzIHNoYXBlIGFzIGFuIGFkZGl0aW9uYWwgY2F0ZWdvcmljYWwgY292YXJpYXRlIiwNCiAgICAgICAgY29sLm5hbWVzID0gYygiVGF4b24iLCJTcGVjaW1lbiIsIkFjdHVhbCBMZW5ndGgiLCJFc3QuIiwiKy8tICVQRSIsIjk1JSBQLkkuIikpJT4lDQogIGNvbHVtbl9zcGVjKDEsIGl0YWxpYyA9IFQpJT4lDQogIGNvbHVtbl9zcGVjKDQsIGJvbGQgPSBUKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQojIyBFeGNsdWRpbmcgYWNhbnRob3B0ZXJ5Z2lhbiBmaXNoZXMNCg0KQmVjYXVzZSBzb21lIG9mIHRoZSBoaWdoZXN0IHN5c3RlbWF0aWMgcmVzaWR1YWxzIGluIHRoZSBtb2RlbCB0aGF0IGNvdWxkIG5vdCBiZSBleHBsYWluZWQgYnkgY2hhbmdlcyBpbiBib2R5IHNoYXBlIHdlcmUgaW4gYWNhbnRob3B0ZXJ5Z2lhbiBjbGFkZXMgKGkuZS4sIFNlcnJhbmlkYWUsIEhvbG9jZW50cmlkYWUsIGV0Yy4pIGFuZCBhY2FudGhvcHRlcnlnaWFucyBpbiBnZW5lcmFsIG9mdGVuIHNob3cgc2hvcnRlbmVkIG9yIGNvbXByZXNzaWZvcm0gYm9kaWVzIHdpdGggYW50ZXJpb3JseSBwb3NpdGlvbmVkIHBlbHZpYyBnaXJkbGVzIChhbmQgdGhlIGFzc29jaWF0ZWQgc2lkZSBlZmZlY3RzIG9uIGJvZHkgc2hhcGUpLCBpdCBpcyB3b3J0aCBjb25zaWRlcmluZyBpZiB0aGUgc2hvcnRlciBsZW5ndGhzIGZvciAqRHVua2xlb3N0ZXVzIHRlcnJlbGxpKiBwcmVkaWN0ZWQgaGVyZSBtaWdodCBiZSBiaWFzZWQgYnkgdGhlIGluY2x1c2lvbiBvZiBhY2FudGhvcHRlcnlnaWFuIGxpbmVhZ2VzLCB3aGljaCBhcmUgZXhwZWN0ZWQgdG8gbm90IGJlIHZlcnkgc2ltaWxhciB0byBwbGFjb2Rlcm1zIGluIGJvZHkgc2hhcGUuDQoNCkJlY2F1c2Ugc28gbWFueSBub24tYWNhbnRob3B0ZXJ5Z2lhbiBhY3Rpbm9wdGVyeWdpYW5zIGhhdmUgZWxvbmdhdGUgYm9kaWVzIChnYXJzLCBwb2x5cHRlcmlkcywgZWVscywgZXRjLikgdGhhdCBhcmUgbm90IHR5cGljYWwgZm9yIGZpc2hlcyBtb3JlIGdlbmVyYWxseSAoZXNwZWNpYWxseSB3aGVuIGNvbXBhcmVkIGFnYWluc3QgdGhlIG5vbi1hY2FudGhvcHRlcnlnaWFuIGZvc3NpbCByZWNvcmQpLCBpdCBpcyB3b3J0aCBjb250cm9sbGluZyBmb3IgdGhlIGVmZmVjdHMgb2YgYm9keSBzaGFwZSBvbiB0aGUgbW9kZWwgYnkgZXhjbHVkaW5nIGhpZ2hseSBlbG9uZ2F0ZSB0YXhhLiBNb2RlbHMgd2VyZSBmaXQgZG9pbmcgYm90aCBiZWxvdy4NCg0KYGBge3J9DQpmaXQubm9fYWNhbnRob3B0ZXJ5Z2lpPC1sbShsb2codG90YWxfbGVuZ3RoKX5sb2coT09MKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhX2ZpbmFsJT4lZmlsdGVyKGhpZ2hlcl9ncm91cCE9IkFjYW50aG9wdGVyZ3lpaSIpKQ0KZml0Lm5vX2FjYW50aG9wdGVyeWdpaTI8LWxtKGxvZyh0b3RhbF9sZW5ndGgpfmxvZyhPT0wpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfZmluYWwlPiVmaWx0ZXIoaGlnaGVyX2dyb3VwIT0iQWNhbnRob3B0ZXJneWlpIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNoYXBlICVpbiUgYygiZWxvbmdhdGUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImZ1c2lmb3JtIikpKQ0KZml0Lm5vX2FjYW50aG9wdGVyeWdpaTM8LWxtKGxvZyh0b3RhbF9sZW5ndGgpfmxvZyhPT0wpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfZmluYWwlPiVmaWx0ZXIoaGlnaGVyX2dyb3VwIT0iQWNhbnRob3B0ZXJneWlpIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNoYXBlPT0iZnVzaWZvcm0iKSkNCnN1bW1hcnkoZml0Lm5vX2FjYW50aG9wdGVyeWdpaSkNCnJiaW5kKA0KICAiQWxsIFRheGEiPXJlZ3Jlc3Npb24uc3RhdHMoZml0Lm5vX2FjYW50aG9wdGVyeWdpaSksDQogICJFeGNsdWRpbmcgYW5ndWlsbGlmb3JtIGFuZCBtYWNydXJpZm9ybSB0YXhhIj1yZWdyZXNzaW9uLnN0YXRzKGZpdC5ub19hY2FudGhvcHRlcnlnaWkyKSwNCiAgIkZ1c2lmb3JtIHRheGEgb25seSI9cmVncmVzc2lvbi5zdGF0cyhmaXQubm9fYWNhbnRob3B0ZXJ5Z2lpMykpDQpgYGANCg0KYGBge3J9DQpmb3NzaWxfdGF4YSU+JQ0KICBmaWx0ZXIobGVuZ3RoX2FzPT0idG90YWwgbGVuZ3RoInxzcGVjaW1lbiAlaW4lIGMoIkNNTkggNTc2OCIsIkNNTkggNzQyNCIsIkNNTkggNjA5MCIsIkNNTkggNzA1NCIpKSU+JQ0KICBhdWdtZW50KGZpdC5ub19hY2FudGhvcHRlcnlnaWksbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0aW9uIiklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCJmaXQxIiwuKSkpJT4lDQogIGF1Z21lbnQoZml0Lm5vX2FjYW50aG9wdGVyeWdpaTIsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0aW9uIiklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCJmaXQyIiwuKSkpJT4lDQogIGF1Z21lbnQoZml0Lm5vX2FjYW50aG9wdGVyeWdpaTMsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0aW9uIiklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCJmaXQzIiwuKSkpJT4lDQogIG11dGF0ZShhY3Jvc3MoZml0MS5maXR0ZWQ6Zml0MS51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0Lm5vX2FjYW50aG9wdGVyeWdpaSkkQ0YpLA0KICAgICAgICAgYWNyb3NzKGZpdDIuZml0dGVkOmZpdDIudXBwZXIsfmV4cCguKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5ub19hY2FudGhvcHRlcnlnaWkyKSRDRiksDQogICAgICAgICBhY3Jvc3MoZml0My5maXR0ZWQ6Zml0My51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0Lm5vX2FjYW50aG9wdGVyeWdpaTMpJENGKSklPiUNCiAgc2VsZWN0KHRheG9uLHNwZWNpbWVuLHRvdGFsX2xlbmd0aCxmaXQxLmZpdHRlZCxmaXQxLmxvd2VyLGZpdDEudXBwZXIsDQogICAgICAgICBmaXQyLmZpdHRlZCxmaXQyLmxvd2VyLGZpdDIudXBwZXIsDQogICAgICAgICBmaXQzLmZpdHRlZCxmaXQzLmxvd2VyLGZpdDMudXBwZXIpJT4lDQogIGFycmFuZ2UoZml0MS5maXR0ZWQpJT4lDQogIGthYmxlKGRpZ2l0cz0xLGFsaWduPWMoImwiLCJjIiwiYyIsImMiLCJjIiwiYyIsImMiLCJjIiwiYyIsImMiLCJjIiwiYyIpLA0KICAgICAgICBjYXB0aW9uPSAiTGVuZ3RoIGVzdGltYXRlcyBmb3Igc3BlY2ltZW5zIG9mIDxpPkR1bmtsZW9zdGV1cyB0ZXJyZWxsaTwvaT4gdXNpbmcgYSBtb2RlbCB0aGF0IGV4Y2x1ZGVzIGFjYW50aG9wdGVyeWdpYW4gZmlzaGVzIiwNCiAgICAgICAgY29sLm5hbWVzID0gYygiVGF4b24iLCJTcGVjaW1lbiIsIkFjdHVhbCBMZW5ndGgiLCJFc3QuIiwiTHdyIDk1JSBQLkkuIiwiVXByIDk1JSBQLkkuIiwNCiAgICAgICAgICAgICAgICAgICAgICAiRXN0LiIsIkx3ciA5NSUgUC5JLiIsIlVwciA5NSUgUC5JLiIsDQogICAgICAgICAgICAgICAgICAgICAgIkVzdC4iLCJMd3IgOTUlIFAuSS4iLCJVcHIgOTUlIFAuSS4iKSklPiUNCiAgY29sdW1uX3NwZWMoMSwgaXRhbGljID0gVCklPiUNCiAgY29sdW1uX3NwZWMoYygzLDQsNywxMCksIGJvbGQgPSBUKSU+JQ0KICBhZGRfaGVhZGVyX2Fib3ZlKGMoIiAiPTMsIkFsbCBub24tYWNhbnRob3B0ZXJ5Z2lhbiBmaXNoZXMiPTMsIkV4Y2x1ZGluZyBhbmd1aWxsaWZvcm0gYW5kIG1hY3J1cmlmb3JtIHRheGEiPTMsIkZ1c2lmb3JtIG5vbi1hY2FudGhvcHRlcnlnaWFucyBvbmx5Ij0zKSklPiUNCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KSG93ZXZlciwgZXZlbiB3aGVuIGV4Y2x1ZGluZyBhY2FudGhvcHRlcnlnaWFuIGZpc2hlcywgZXN0aW1hdGVkIGxlbmd0aHMgZm9yICpEdW5rbGVvc3RldXMqIHdlcmUgY29tcGFyYWJsZSB0byB0aG9zZSBvZiB0aGUgYWNhbnRob3B0ZXJ5Z2lhbi1vbmx5IG1vZGVsLiBUaHVzLCB0aGUgc2hvcnQgbGVuZ3RocyBmb3IgKkR1bmtsZW9zdGV1cyogcHJlZGljdGVkIGhlcmUgZG8gbm90IGFwcGVhciB0byBiZSBkcml2ZW4gYnkgdmVyeSBzaG9ydC1ib2RpZWQgb3IgbGFyZ2UtaGVhZGVkIGFjYW50aG9wdGVyeWdpYW4gdGF4YS4NCg0KIyMgQ29uc2lkZXJpbmcgYm90aCBjbGFkZSBtZW1iZXJzaGlwIGFuZCBib2R5IHNoYXBlIHsjZml0c2hhcGVjbGFkZX0NCg0KT25lIGlzc3VlIHdpdGggdGhlIHByZXNlbnQgbW9kZWwgaXMgbW9zdCBvZiB0aGUgdmVyeSBsYXJnZXN0IGZpc2hlcyAoMysgbSkgc2FtcGxlZCB0ZW5kIHRvIGJlIHBlbGFnaWMgTGFtbmlmb3JtZXMgb3IgKkVjaGlub3JoaW51cyogc3BwLiB0aGF0IGFyZSBrbm93biB0byBoYXZlIHZlcnkgbG9uZyBoZWFkcyByZWxhdGl2ZSB0byBib2R5IGxlbmd0aCwgd2hlcmVhcyBzb21lIG9mIHRoZSBtb3N0IGhlYXZpbHkgc2FtcGxlZCBmaXNoZXMgbGFyZ2VyIHRoYW4gMSBtIChpLmUuLCBiZXR3ZWVuIDEgYW5kIDIuNSBtKSBhcmUgYWN0aW5vcHRlcnlnaWFuIGZpc2hlcyB3aXRoIGVsb25nYXRlIHRydW5rIHByb3BvcnRpb25zIChlLmcuLCAqVGV0cmFwdHVydXMqLCAqS2FqaWtpYSosICpDb3J5cGhhZW5hKikuIFRoaXMgY291bGQgcG90ZW50aWFsbHkgY2F1c2UgYmlhc2VzIGluIGVzdGltYXRlcyBvZiBib2R5IGxlbmd0aCBmb3IgKkR1bmtsZW9zdGV1cyosIGVzcGVjaWFsbHkgZ2l2ZW4gdGhhdCBMYW1uaWZvcm1lcyBhbmQgRWNoaW5vcmhpbmlmb3JtZXMgdGVuZCB0byBiaWFzIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIGB0b3RhbF9sZW5ndGhgIGFuZCBgT09MYCBmb3Igc2hhcmtzLg0KDQpCZWNhdXNlIG9mIHRoaXMsIGEgbW9kZWwgd2FzIGZpdCBjb250YWluaW5nIGJvdGggYGNsYWRlYCBhbmQgYHNoYXBlYCwgYXNzdW1pbmcgdGhhdCBgc2hhcGVgIGluZmx1ZW5jZXMgdGhlIG1vZGVsIGFzIGFuIGluZGVwZW5kZW50IHZhcmlhYmxlIChnaXZlbiB0aGUgcmVzaWR1YWxzIGR1ZSB0byBzaGFwZSBhcmUgcHJpbWFyaWx5IHNlZW4gaW4gdGhlIGludGVyY2VwdCkgd2hlcmVhcyBkaWZmZXJlbnQgbGV2ZWxzIG9mIGBjbGFkZWAgd2VyZSBhbGxvd2VkIHRvIGhhdmUgZGlmZmVyZW50IHNsb3BlcyAoZ2l2ZW4gTGFtbmlmb3JtZXMgYW5kIEVjaGlub3JoaW5pZm9ybWVzIGRyaXZlIGEgZGlmZmVyZW50IHBhdHRlcm4gaW4gc2hhcmtzKS4gVGhpcyB3b3VsZCBiZSBleHBlY3RlZCB0byBlc3RpbWF0ZSBsZW5ndGggaW4gKkR1bmtsZW9zdGV1cyB0ZXJyZWxsaSogd2l0aG91dCBpdCBiZWluZyBiaWFzZWQgZWl0aGVyIGJ5IGZpc2hlcyB3aXRoIGVsb25nYXRlIGJvZHkgc2hhcGVzIG9yIGJ5IHNob3J0LWJvZGllZCBsYW1uaWRzLg0KDQpbTm90ZTogaW4gdGhpcyBtb2RlbCBtZW1iZXJzaGlwIGluIFNlcnJhbmlkYWUgb3IgSG9sb2NlbnRyaWRhZSB3YXMgdHJlYXRlZCBhcyBhIGZhY3RvciBhcyB3ZWxsLCBnaXZlbiB0aGF0IGdyb3VwZXJzIGFuZCBzcXVpcnJlbGZpc2hlcyBoYXZlIHVudXN1YWwgcHJvcG9ydGlvbnMgY29tcGFyZWQgdG8gb3RoZXIgZmlzaGVzXSgjZnVzaWZvcm1ncmFwaCkNCg0KYGBge3J9DQpmaXQuc2hhcGVfY2xhZGU8LWxtKGxvZyh0b3RhbF9sZW5ndGgpfmxvZyhPT0wpKmNsYWRlK3NoYXBlKyhmYW1pbHk9PSJTZXJyYW5pZGFlIikrKGZhbWlseT09IkhvbG9jZW50cmlkYWUiKSwNCiAgICAgICAgICAgICAgICAgICAgZGF0YV9maW5hbCkNCmZpdC5zaGFwZV9jbGFkZV9zcGVjaWVzX2F2ZXJhZ2VzPC1sbShsb2codG90YWxfbGVuZ3RoKX5sb2coT09MKSpjbGFkZStzaGFwZSsoZmFtaWx5PT0iU2VycmFuaWRhZSIpKyhmYW1pbHk9PSJIb2xvY2VudHJpZGFlIiksDQogICAgICAgICAgICAgICAgICAgIGRhdGFfc3BlY2llcykNCmZpdC5zaGFwZV9jbGFkZTM8LWxtKGxvZyh0b3RhbF9sZW5ndGgpfmxvZyhPT0wpKihjbGFkZT09IkNob25kcmljaHRoeWVzIikrDQogICAgICAgICAgICAgICAgICAgICAgIHNoYXBlKyhmYW1pbHk9PSJTZXJyYW5pZGFlIikrKGZhbWlseT09IkhvbG9jZW50cmlkYWUiKSwNCiAgICAgICAgICAgICAgICAgICAgZGF0YV9maW5hbCkNCmBgYA0KDQojIyMgTW9kZWwgaW5jbHVkaW5nIGNsYWRlIGFuZCBib2R5IHNoYXBlLCB1c2luZyBpbmRpdmlkdWFsIHNwZWNpbWVucyB7I3N1bW1hcnlzaGFwZWNsYWRlfQ0KDQpgYGB7cn0NCnN1bW1hcnkoZml0LnNoYXBlX2NsYWRlKQ0KYGBgDQoNCiMjIyBNb2RlbCBpbmNsdWRpbmcgY2xhZGUgYW5kIGJvZHkgc2hhcGUsIHVzaW5nIHNwZWNpZXMgYXZlcmFnZXMNCg0KYGBge3J9DQpzdW1tYXJ5KGZpdC5zaGFwZV9jbGFkZV9zcGVjaWVzX2F2ZXJhZ2VzKQ0KYGBgDQoNCiMjIyBNb2RlbCBpbmNsdWRpbmcgY2xhZGUgYW5kIGJvZHkgc2hhcGUsIG9ubHkgY29uc2lkZXJpbmcgbWVtYmVyc2hpcCBpbiBDaG9uZHJpY2h0aHllcyBhcyBpbXBvcnRhbnQgcGh5bG9nZW5ldGljIGluZm9ybWF0aW9uDQoNCkhvd2V2ZXIsIG9uZSBpc3N1ZSB3aXRoIHRoaXMgaXMgdGhlIHNtYWxsIG51bWJlciBvZiBhcnRocm9kaXJlIHRheGEgZm9yIHdoaWNoIHRvdGFsIGxlbmd0aCBkYXRhIHdhcyBhdmFpbGFibGUuIFRoaXMgbWVhbnMgdGhhdCB0aGUgY29lZmZpY2llbnQgZm9yIHBsYWNvZGVybXMgaGFzIGEgZ3JlYXQgZGVncmVlIG9mIHVuY2VydGFpbnR5ICg9c3RhbmRhcmQgZXJyb3IpLiBbRXhhbWluaW5nIHRoZSByZXN1bHRzIG9mIHRoZSBtb2RlbCBjb250YWluaW5nIGRpZmZlcmVudCBjb2VmZmljaWVudHMgZm9yIG1ham9yIGZpc2ggY2xhZGVzXSgjc3VtbWFyeXNoYXBlY2xhZGUpIGZvdW5kIHRoYXQgKm9ubHkqIENob25kcmljaHRoeWVzIHNpZ25pZmljYW50bHkgZGlmZmVyZWQgZnJvbSB0aGUgb3RoZXIgZmlzaCBncm91cHMgaW4gc2xvcGUsIGxhcmdlbHkgYmVjYXVzZSB0aGUgbGFyZ2VzdCBjaG9uZHJpY2h0aHlhbnMgYXJlIGFsbCBzaG9ydC1ib2RpZWQgKG1vc3RseSBsYW1uaWRzIG9yIG90aGVyIGxhbW5pZm9ybXMgbGlrZSAqTWVnYWNoYXNtYSopLiBUaGlzIHN1Z2dlc3RzIGl0IG1pZ2h0IGJlIGJldHRlciB0byBzaW1wbHkgY29uc2lkZXIgYSBtb2RlbCB0cmVhdGluZyBtZW1iZXJzaGlwIGluIENob25kcmljaHRoeWVzIGFzIGEgYmluYXJ5IHZhcmlhYmxlLCBhcyB0aGlzIHdvdWxkIGdyZWF0bHkgbG93ZXIgdW5jZXJ0YWludHkgaW4gdGhlIGNvZWZmaWNpZW50IGZvciBgY2xhZGVgIGFuZCB0aHVzIGltcHJvdmUgbW9kZWwgYWNjdXJhY3kgd2hlbiBlc3RpbWF0aW5nIGxlbmd0aCBpbiAqRHVua2xlb3N0ZXVzKiBhbmQgb3RoZXIgYXJ0aHJvZGlyZXMuDQoNCmBgYHtyfQ0Kc3VtbWFyeShmaXQuc2hhcGVfY2xhZGUzKQ0KYGBgDQoNCiMjIyBSZWdyZXNzaW9uIHN0YXRpc3RpY3MNCg0KYGBge3J9DQpyYmluZCgiSW5kaXZpZHVhbCBzcGVjaW1lbnMiPXJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlKSwNCiAgICAgICJTcGVjaWVzIGF2ZXJhZ2VzIj1yZWdyZXNzaW9uLnN0YXRzKGZpdC5zaGFwZV9jbGFkZV9zcGVjaWVzX2F2ZXJhZ2VzKSwNCiAgICAgICJPbmx5IGNvbnNpZGVyaW5nIG1lbWJlcnNoaXAgaW4gQ2hvbmRyaWNodGh5ZXMiPXJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlMykpDQpgYGANCg0KIyMjIFByZWRpY3RpbmcgbGVuZ3RoIGluICpEdW5rbGVvc3RldXMgdGVycmVsbGkqIGFuZCBvdGhlciBhcnRocm9kaXJlcw0KDQpgYGB7cn0NCmZvc3NpbF90YXhhJT4lDQogIGZpbHRlcighIWZvc3NpbC5zcGVjaW1lbnMpJT4lDQogIG11dGF0ZShJc0Nob25kcmljaHRoeWVzPWlmZWxzZShjbGFkZT09IkNob25kcmljaHRoeWVzIixULEYpKSU+JQ0KICBhdWdtZW50KGZpdC5zaGFwZV9jbGFkZSxuZXdkYXRhPS4saW50ZXJ2YWw9InByZWRpY3QiKSU+JQ0KICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnLicpKSwgZnVucyhwYXN0ZTAoImZpdDEiLC4pKSklPiUNCiAgYXVnbWVudChmaXQuc2hhcGVfY2xhZGVfc3BlY2llc19hdmVyYWdlcyxuZXdkYXRhPS4saW50ZXJ2YWw9InByZWRpY3Rpb24iKSU+JQ0KICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnLicpKSwgZnVucyhwYXN0ZTAoImZpdDIiLC4pKSklPiUNCiAgYXVnbWVudChmaXQuc2hhcGVfY2xhZGUzLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdGlvbiIpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgiZml0MyIsLikpKSU+JQ0KICBtdXRhdGUoYWNyb3NzKGZpdDEuZml0dGVkOmZpdDEudXBwZXIsfmV4cCguKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5zaGFwZV9jbGFkZSkkQ0YpLA0KICAgICAgICAgYWNyb3NzKGZpdDIuZml0dGVkOmZpdDIudXBwZXIsfmV4cCguKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5zaGFwZV9jbGFkZV9zcGVjaWVzX2F2ZXJhZ2VzKSRDRiksDQogICAgICAgICBhY3Jvc3MoZml0My5maXR0ZWQ6Zml0My51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlMykkQ0YpKSU+JQ0KICBzZWxlY3QodGF4b24sc3BlY2ltZW4sdG90YWxfbGVuZ3RoLGZpdDEuZml0dGVkLGZpdDEubG93ZXIsZml0MS51cHBlciwNCiAgICAgICAgIGZpdDIuZml0dGVkLGZpdDIubG93ZXIsZml0Mi51cHBlciwNCiAgICAgICAgIGZpdDMuZml0dGVkLGZpdDMubG93ZXIsZml0My51cHBlciklPiUNCiAgYXJyYW5nZShmaXQxLmZpdHRlZCklPiUNCiAga2FibGUoZGlnaXRzPTEsYWxpZ249YygibCIsImMiLCJjIiwiYyIsImMiLCJjIiwiYyIsImMiLCJjIiwiYyIsImMiLCJjIiksDQogICAgICAgIGNhcHRpb249ICJMZW5ndGggZXN0aW1hdGVzIGZvciBzcGVjaW1lbnMgb2YgPGk+RHVua2xlb3N0ZXVzIHRlcnJlbGxpPC9pPiB1c2luZyBhIG1vZGVsIGNvbnNpZGVyaW5nIGJvdGggY2xhZGUgYW5kIGJvZHkgc2hhcGUuICdDaG9uZHJpY2h0aHlhbi1zcGVjaWZpYyBtb2RlbCcgcmVmZXJzIHRvIG1vZGVsIHdoZXJlIG9ubHkgZ3JvdXAgbWVtYmVyc2hpcCBjb25zaWRlcmVkIHdhcyB3aGV0aGVyIG9yIG5vdCB0YXhvbiB3YXMgYSBtZW1iZXIgb2YgQ2hvbmRyaWNodGh5ZXMuIiwNCiAgICAgICAgY29sLm5hbWVzID0gYygiVGF4b24iLCJTcGVjaW1lbiIsIkFjdHVhbCBMZW5ndGgiLCJFc3QuIiwiTHdyIDk1JSBQLkkuIiwiVXByIDk1JSBQLkkuIiwNCiAgICAgICAgICAgICAgICAgICAgICAiRXN0LiIsIkx3ciA5NSUgUC5JLiIsIlVwciA5NSUgUC5JLiIsDQogICAgICAgICAgICAgICAgICAgICAgIkVzdC4iLCJMd3IgOTUlIFAuSS4iLCJVcHIgOTUlIFAuSS4iKSklPiUNCiAgY29sdW1uX3NwZWMoMSwgaXRhbGljID0gVCklPiUNCiAgY29sdW1uX3NwZWMoYygzLDQsNywxMCksIGJvbGQgPSBUKSU+JQ0KICBhZGRfaGVhZGVyX2Fib3ZlKGMoIiAiPTMsIkluZGl2aWR1YWwgU3BlY2ltZW5zIj0zLCJTcGVjaWVzIEF2ZXJhZ2VzIj0zLCJDaG9uZHJpY2h0aHlhbi1TcGVjaWZpYyBNb2RlbCI9MykpJT4lDQogIGthYmxlX3N0eWxpbmcoKQ0KYGBgDQoNCkFzIGFuIGV4YW1wbGUgb2Ygd2hhdCBpcyBtZWFudCBieSB0aGUgc21hbGwgc2FtcGxlIHNpemUgb2YgQXJ0aHJvZGlyYSByZWR1Y2luZyBtb2RlbCBwcmVjaXNpb24sIGNvbXBhcmUgdGhlIHByZWRpY3Rpb24gaW50ZXJ2YWxzIGZvciB0aGUgbW9kZWwgaW5jbHVkaW5nIGBjbGFkZWAgYXMgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSB3aXRoIHRoZSBvbmUgb25seSB1c2luZyBhIGJpbmFyeSB2YXJpYWJsZSBjb25zaWRlcmluZyB3aGV0aGVyIG9yIG5vdCBhIHRheG9uIGJlbG9uZ3MgdG8gQ2hvbmRyaWNodGh5ZXMuIFRoZSBwcmVkaWN0aW9uIGludGVydmFscyBvZiB0aGUgbGF0dGVyIGFyZSBtdWNoIHNtYWxsZXIsIGFuZCB0aGlzIGlzIGJlY2F1c2Ugb2YgdGhlIFttdWNoIGdyZWF0ZXIgc3RhbmRhcmQgZXJyb3IgZm9yIGBjbGFkZVBsYWNvZGVybWlgIGFuZCB0aGUgaW50ZXJhY3Rpb24gYmV0d2VlbiBgUGxhY29kZXJtaWAgYW5kIGBsb2coT09MKWAgY29tcGFyZWQgdG8gQWN0aW5vcHRlcnlnaWkgYW5kIENob25kcmljaHRoeWVzXSgjc3VtbWFyeXNoYXBlY2xhZGUpIHdoZW4gZWFjaCBjbGFkZSBpcyBjb25zaWRlcmVkIGJ5IHRoZW1zZWx2ZXMuDQoNCiMgQWxsb3dpbmcgcmVsYXRpdmUgYm9keSBkZXB0aCB0byBpbmZsdWVuY2UgbGVuZ3RoIGVzdGltYXRlcw0KDQoocmVmOnNoYXBlcmVzaWR1YWxzMikgUGxvdCBiZXR3ZWVuIHJlc2lkdWFscyBvZiB0aGUgYWxsLXNwZWNpbWVuIG1vZGVsIChgZml0Lk9PTGApIGFuZCBhc3BlY3QgcmF0aW8gKGBib2R5X2RlcHRoYC9gdG90YWxfbGVuZ3RoYCkuIFRheGEgd2l0aCBwb3N0ZXJpb3JseSBzaGlmdGVkIG9yYml0cyAoQmFsaXN0b2lkZWEgYW5kIEFjYW50aHVyaWRhZSkgb21pdHRlZCB0byBiZXR0ZXIgc2hvdyBwYXR0ZXJucyBpbiB0aGUgbW9kZWwuIEZvciB0aGUgbG9lc3MgbW9kZWwsICJ1bnVzdWFsIHRheGEiIHJlZmVycyB0byBtZW1iZXJzIG9mIHRoZSBjbGFkZXMgUGV0cm9teXpvbnRpZm9ybWVzLCBTZXJyYW5pZGFlLCBIb2xvY2VucmlkYWUsIEFjYW50aHVyaWRhZSwgQmFsaXN0aWRhZSwgYW5kIE1vbmFjYW50aGlkYWUsIHdoaWNoIGFwcGVhciB0byBzaG93IHVudXN1YWwgcGF0dGVybnMgaW4gcmVzaWR1YWxzIHNlcGFyYXRlIGZyb20gdGhlIGdlbmVyYWwgbm9uLWxpbmVhciBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSByZXNpZHVhbHMgYW5kIGJvZHkgc2hhcGUuDQoNCmBgYHtyLGZpZy5jYXA9IihyZWY6c2hhcGVyZXNpZHVhbHMyKSJ9DQpnZ3Bsb3QoZGF0YV9maW5hbCU+JQ0KICAgICAgICAgZmlsdGVyKCFpcy5uYShPT0wpLCFpcy5uYSh0b3RhbF9sZW5ndGgpLGxlbmd0aF9hcyE9ImVzdGltYXRlZCB0LmwuIiklPiUNCiAgICAgICAgIG11dGF0ZShyZXNpZHVhbHM9cmVzaWR1YWxzKGZpdC5PT0wpLA0KICAgICAgICAgICAgICAgIHNoYXBlPXJlbGV2ZWwoDQogICAgICAgICAgICAgICAgICBmYWN0b3IoaWZlbHNlKGZhbWlseT09IlNlcnJhbmlkYWUiLCJTZXJyYW5pZGFlIixhcy5jaGFyYWN0ZXIoc2hhcGUpKSkscmVmPSJmdXNpZm9ybSIpKSU+JQ0KICAgICAgICAgZmlsdGVyKCFmYW1pbHkgJWluJSBjKCJNb25hY2FudGhpZGFlIiwiQmFsaXN0aWRhZSIsIkFjYW50aHVyaWRhZSIpKSwNCiAgICAgICBhZXMoSShib2R5X2RlcHRoL3RvdGFsX2xlbmd0aCkscmVzaWR1YWxzKSkrDQogIGdlb21fc3RhcihhZXMoZmlsbD1zaGFwZSxzdGFyc2hhcGU9Y2xhZGUpKSsNCiAgZ2VvbV9zbW9vdGgoZGF0YT0uJT4lZmlsdGVyKCFmYW1pbHkgJWluJSBjKCJTZXJyYW5pZGFlIiwgIkJhbGlzdGlkYWUiLCAiSG9sb2NlbnRyaWRhZSIsICJBY2FudGh1cmlkYWUiLCAiTW9uYWNhbnRoaWRhZSIpLGNsYWRlIT0iUGV0cm9teXpvbnRpZm9ybWVzIiksYWVzKGNvbG9yPSIyIiksZm9ybXVsYT15fngsbWV0aG9kPSJsb2VzcyIpKw0KICBnZW9tX3Ntb290aChmb3JtdWxhPXl+eCxtZXRob2Q9ImxtIixhZXMoY29sb3I9IjEiKSkrDQogIHNjYWxlX3N0YXJzaGFwZV9tYW51YWwodmFsdWVzPWMoMTUsMTEsMTMsMSwyOCkpKw0KICBzY2FsZV9jb2xvcl9tYW51YWwobGFiZWxzPWMoIkxtIG1vZGVsIG9mIGFsbCB0YXhhIiwiTG9lc3MgZml0IGV4Y2x1ZGluZyB1bnVzdWFsIHRheGEiKSx2YWx1ZXM9YyhodWVfcGFsKCkoMikpKSsNCiAgbGFicyh4PSJBc3BlY3QgUmF0aW8gb2YgQm9keSAoYm9keSBkZXB0aC90b3RhbCBsZW5ndGgpIix5PSJSZXNpZHVhbHMgb2YgT09MIEVxdWF0aW9uIiwNCiAgICAgICBzdGFyc2hhcGU9IkNsYWRlIixmaWxsPSJTaGFwZSIsY29sb3VyPSJDb2xvciIpKw0KICB0aGVtZShsZWdlbmQuYm94ID0gImhvcml6b250YWwiLGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC41NSwwLjcpKSsNCiAgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzdGFyc2hhcGUgPSAxNSkpKQ0KYGBgDQoNCk5vdGUgdGhlIG5vbi1saW5lYXIgcmVsYXRpb25zaGlwIGJldHdlZW4gYm9keSBzaGFwZSBhbmQgdGhlIHJlc2lkdWFscyBvZiB0aGUgbW9kZWwsIGluIHdoaWNoIHRoZXJlIGlzIG5leHQgdG8gbm8gY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgcmVzaWR1YWxzIGFuZCBib2R5IHNoYXBlIGFtb25nIGNvbXByZXNzaWZvcm0gdGF4YSwgYnV0IHRoZXJlIGlzIGEgcGF0dGVybiBvZiBldmVyLWluY3JlYXNpbmcgcmVzaWR1YWxzIGFtb25nIGVsb25nYXRlIGFuZCBhbmd1aWxsaWZvcm0gdGF4YSAoZXNwZWNpYWxseSBhbmd1aWxsaWZvcm0gZ25hdGhvc3RvbWVzKSwgcmVwcmVzZW50aW5nIGFuIGFjY2VsZXJhdGlvbiBpbiBheGlhbCBlbG9uZ2F0aW9uIHJlbGF0aXZlIHRvIHRoZSBoZWFkIHdpdGggaW5jcmVhc2luZyBhc3BlY3QgcmF0aW8uDQoNCmBgYHtyfQ0Kc3VtbWFyeShsbShyZXNpZHVhbHN+SShib2R5X2RlcHRoL3RvdGFsX2xlbmd0aCksDQogICAgICAgICAgIGRhdGFfZmluYWwlPiUNCiAgICAgICAgIGZpbHRlcighaXMubmEoT09MKSwhaXMubmEodG90YWxfbGVuZ3RoKSxsZW5ndGhfYXMhPSJlc3RpbWF0ZWQgdC5sLiIpJT4lDQogICAgICAgICBtdXRhdGUocmVzaWR1YWxzPXJlc2lkdWFscyhmaXQuT09MKSkpKQ0KYGBgDQoNCkFzIG5vdGVkIGJ5IHRoZSBhYm92ZSByZXBvcnQsIHRoZXJlIGlzIGEgc2lnbmlmaWNhbnQgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgcmVzaWR1YWxzIG9mIHRoZSBtb2RlbCBhbmQgYXNwZWN0IHJhdGlvLg0KDQojIyBGaXR0aW5nIG1vZGVscyBpbmNsdWRpbmcgYm9keSBoZWlnaHQgYXMgYW4gZXhwbGFuYXRvcnkgdmFyaWFibGUNCg0KIyMjIE1vZGVsIGluY2x1ZGluZyBgYm9keV9kZXB0aGAgYXMgYWRkaXRpb25hbCB2YXJpYWJsZSwgYm90aCBhcyBhIHNlcGFyYXRlIGNvZWZmaWNpZW50IGFuZCBjb25zaWRlcmluZyBpbnRlcmFjdGlvbiBlZmZlY3RzIHdpdGggYE9PTGANCg0KYGBge3J9DQpmaXQucmVsYXRpdmVkZXB0aDE8LWxtKGxvZyh0b3RhbF9sZW5ndGgpfg0KICAgICAgICAgICAgICAgICAgICAgICAgbG9nKE9PTCkqSShib2R5X2RlcHRoKSwNCiAgICAgICAgICAgICAgICAgICAgICBkYXRhX2ZpbmFsICU+JSBmaWx0ZXIobGVuZ3RoX2FzIT0iZXN0aW1hdGVkIHQubC4iKSkNCnN1bW1hcnkoZml0LnJlbGF0aXZlZGVwdGgxKQ0KYGBgDQoNCiMjIyBNb2RlbCBpbmNsdWRpbmcgaW50ZXJhY3Rpb24gYmV0d2VlbiBgYm9keV9kZXB0aGAgYW5kIGBPT0xgIG9ubHkNCg0KYGBge3J9DQpmaXQucmVsYXRpdmVkZXB0aDI8LWxtKGxvZyh0b3RhbF9sZW5ndGgpfg0KICAgICAgICAgICAgICAgICAgICAgICAgbG9nKE9PTCkqbG9nKGJvZHlfZGVwdGgpLWxvZyhib2R5X2RlcHRoKSwNCiAgICAgICAgICAgICAgICAgICAgICBkYXRhX2ZpbmFsICU+JSBmaWx0ZXIobGVuZ3RoX2FzIT0iZXN0aW1hdGVkIHQubC4iKSkNCnN1bW1hcnkoZml0LnJlbGF0aXZlZGVwdGgyKQ0KYGBgDQoNCiMjIyBNb2RlbCBpbmNsdWRpbmcgY29lZmZpY2llbnQgb25seQ0KDQpgYGB7cn0NCmZpdC5yZWxhdGl2ZWRlcHRoMzwtbG0obG9nKHRvdGFsX2xlbmd0aCl+DQogICAgICAgICAgICAgICAgICAgICAgICBsb2coT09MKStsb2coYm9keV9kZXB0aCksDQogICAgICAgICAgICAgICAgICAgICAgZGF0YV9maW5hbCAlPiUgZmlsdGVyKGxlbmd0aF9hcyE9ImVzdGltYXRlZCB0LmwuIikpDQpzdW1tYXJ5KGZpdC5yZWxhdGl2ZWRlcHRoMykNCmBgYA0KDQojIyMgTW9kZWwgdXNpbmcgaW50ZWdlciBvZiBgaGVhZCBsZW5ndGhgL2Bib2R5X2RlcHRoYCB0byBtb2RlbCBlZmZlY3RzIG9mIHNoYXBlIG9uIHJlZ3Jlc3Npb24NCg0KYGBge3J9DQpmaXQucmVsYXRpdmVkZXB0aDQ8LWxtKGxvZyh0b3RhbF9sZW5ndGgpfg0KICAgICAgICAgICAgICAgICAgICAgICAgbG9nKE9PTCkrSShoZWFkX2xlbmd0aC9ib2R5X2RlcHRoKSwNCiAgICAgICAgICAgICAgICAgICAgICBkYXRhX2ZpbmFsICU+JSBmaWx0ZXIobGVuZ3RoX2FzIT0iZXN0aW1hdGVkIHQubC4iKSkNCnN1bW1hcnkoZml0LnJlbGF0aXZlZGVwdGg0KQ0KYGBgDQoNCiMjIFN1cHBvcnQgc3RhdGlzdGljcyBvZiBtb2RlbHMNCg0KYGBge3J9DQpyYmluZCgiV2l0aCBubyBTaGFwZSBJbmZvcm1hdGlvbiI9cmVncmVzc2lvbi5zdGF0cyhmaXQuT09MKSwNCiAgICAgICJXaXRoIEJvZHkgU2hhcGUgYXMgQ2F0ZWdvcmljYWwgVmFyaWFibGUiPXJlZ3Jlc3Npb24uc3RhdHMoZml0LndpdGhfc2hhcGUpLA0KICAgICAgIldpdGggQ29lZmZpY2llbnQgYW5kIEludGVyYWN0aW9uIFRlcm0gZnJvbSBCb2R5IEhlaWdodCI9cmVncmVzc2lvbi5zdGF0cyhmaXQucmVsYXRpdmVkZXB0aDEpLA0KICAgICAgIldpdGggSW50ZXJhY3Rpb24gVGVybSBPbmx5Ij1yZWdyZXNzaW9uLnN0YXRzKGZpdC5yZWxhdGl2ZWRlcHRoMiksDQogICAgICAiV2l0aCBDb2VmZmljaWVudCBPbmx5Ij1yZWdyZXNzaW9uLnN0YXRzKGZpdC5yZWxhdGl2ZWRlcHRoMyksDQogICAgICAiVXNpbmcgSW50ZWdlciBvZiBIZWFkIExlbmd0aC9Cb2R5IEhlaWdodCI9cmVncmVzc2lvbi5zdGF0cyhmaXQucmVsYXRpdmVkZXB0aDQpKQ0KYGBgDQoNCkhvd2V2ZXIsIHdoZW4gY29tcGFyaW5nIHRoZSBtb2RlbCBzdXBwb3J0IHN0YXRpc3RpY3MsIHdoaWxlIGl0IGNhbiBiZSBzZWVuIHRoYXQgaW5jbHVkaW5nIHJlbGF0aXZlIGJvZHkgaGVpZ2h0IGFzIGEgY292YXJpYXRlIHByb2R1Y2VzIHNsaWdodGx5IGJldHRlciBzdXBwb3J0IGluIHRlcm1zIG9mIEFJQywgQklDLCBhbmQgJVBFIHZhbHVlcyB0aGFuIGEgbW9kZWwgdGhhdCBkb2VzIG5vdCBjb25zaWRlciBib2R5IHNoYXBlLCB0aGV5IHBlcmZvcm0gbXVjaCBtb3JlIHBvb3JseSB0aGFuIG1vZGVscyB0aGF0IHNpbXBseSBicnV0ZS1mb3JjZXMgdmFyaWF0aW9uIGluIGJvZHkgc2hhcGUgaW50byBhIHNpbmdsZSBjYXRlZ29yaWNhbCB2YXJpYWJsZS4gVGh1cywgaXQgaXMgY2xlYXIgdGhhdCBhbHRob3VnaCBjb3ZhcmlhdGlvbiBiZXR3ZWVuIGJvZHkgc2hhcGUgYW5kIGFzcGVjdCByYXRpbyBhcmUgYWZmZWN0aW5nIGxlbmd0aCBlc3RpbWF0ZXMsIHNpbXBseSBhZGRpbmcgaW4gYm9keSBoZWlnaHQgYXMgYSBjb3ZhcnlpbmcgdGVybSBkb2VzIG5vdCBwcm9kdWNlIGEgc2lnbmlmaWNhbnQgaW1wcm92ZW1lbnQgaW4gcmVzdWx0cyBpbiB0aGUgbW9kZWwgaGVyZS4NCg0KIyMgRXN0aW1hdGVkIGxlbmd0aHMgZm9yICpEdW5rbGVvc3RldXMqDQoNCmBgYHtyfQ0KZm9zc2lsX3RheGElPiUNCiAgZmlsdGVyKCEhZm9zc2lsLnNwZWNpbWVucywhaXMubmEoYm9keV9kZXB0aCksY2xhZGU9PSJQbGFjb2Rlcm1pIiklPiUNCiAgYXVnbWVudChmaXQucmVsYXRpdmVkZXB0aDEsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0aW9uIiklPiUNCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0LnJlbGF0aXZlZGVwdGgxKSRDRikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgiZml0MSIsLikpKSU+JQ0KICBhdWdtZW50KGZpdC5yZWxhdGl2ZWRlcHRoMixuZXdkYXRhPS4saW50ZXJ2YWw9InByZWRpY3Rpb24iKSU+JQ0KICBtdXRhdGUoYWNyb3NzKC5maXR0ZWQ6LnVwcGVyLH5leHAoLikqcmVncmVzc2lvbi5zdGF0cyhmaXQucmVsYXRpdmVkZXB0aDIpJENGKSklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCJmaXQyIiwuKSkpJT4lDQogIGF1Z21lbnQoZml0LnJlbGF0aXZlZGVwdGgzLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdGlvbiIpJT4lDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5yZWxhdGl2ZWRlcHRoMykkQ0YpKSU+JQ0KICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnLicpKSwgZnVucyhwYXN0ZTAoImZpdDMiLC4pKSklPiUNCiAgYXVnbWVudChmaXQucmVsYXRpdmVkZXB0aDQsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0aW9uIiklPiUNCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0LnJlbGF0aXZlZGVwdGg0KSRDRikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgiZml0NCIsLikpKSU+JQ0KICBhcnJhbmdlKGZpdDEuZml0dGVkKSU+JQ0KICBtdXRhdGUocmFuZ2UxPXBhc3RlMCgiKCIscm91bmQoZml0MS5sb3dlciwxKSwiLSIscm91bmQoZml0MS51cHBlciwxKSwiKSIpLA0KICAgICAgICAgcmFuZ2UyPXBhc3RlMCgiKCIscm91bmQoZml0Mi5sb3dlciwxKSwiLSIscm91bmQoZml0Mi51cHBlciwxKSwiKSIpLA0KICAgICAgICAgcmFuZ2UzPXBhc3RlMCgiKCIscm91bmQoZml0My5sb3dlciwxKSwiLSIscm91bmQoZml0My51cHBlciwxKSwiKSIpLA0KICAgICAgICAgcmFuZ2U0PXBhc3RlMCgiKCIscm91bmQoZml0NC5sb3dlciwxKSwiLSIscm91bmQoZml0NC51cHBlciwxKSwiKSIpLA0KICAgICAgICAgUEUxX2xvd2VyPWZpdDEuZml0dGVkKigxLShyZWdyZXNzaW9uLnN0YXRzKGZpdC5yZWxhdGl2ZWRlcHRoMSkkYWRqUEUpLzEwMCksDQogICAgICAgICBQRTFfdXBwZXI9Zml0MS5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnJlbGF0aXZlZGVwdGgxKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIFBFMl9sb3dlcj1maXQyLmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQucmVsYXRpdmVkZXB0aDIpJGFkalBFKS8xMDApLA0KICAgICAgICAgUEUyX3VwcGVyPWZpdDIuZml0dGVkKigxKyhyZWdyZXNzaW9uLnN0YXRzKGZpdC5yZWxhdGl2ZWRlcHRoMikkYWRqUEUpLzEwMCksDQogICAgICAgICBQRTNfbG93ZXI9Zml0My5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnJlbGF0aXZlZGVwdGgzKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIFBFM191cHBlcj1maXQzLmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQucmVsYXRpdmVkZXB0aDMpJGFkalBFKS8xMDApLA0KICAgICAgICAgUEU0X2xvd2VyPWZpdDQuZml0dGVkKigxLShyZWdyZXNzaW9uLnN0YXRzKGZpdC5yZWxhdGl2ZWRlcHRoNCkkYWRqUEUpLzEwMCksDQogICAgICAgICBQRTRfdXBwZXI9Zml0NC5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnJlbGF0aXZlZGVwdGg0KSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIFBFX3JhbmdlMT1wYXN0ZTAoIigiLHJvdW5kKFBFMV9sb3dlciwxKSwiLSIscm91bmQoUEUxX3VwcGVyLDEpLCIpIiksDQogICAgICAgICBQRV9yYW5nZTI9cGFzdGUwKCIoIixyb3VuZChQRTJfbG93ZXIsMSksIi0iLHJvdW5kKFBFMl91cHBlciwxKSwiKSIpLA0KICAgICAgICAgUEVfcmFuZ2UzPXBhc3RlMCgiKCIscm91bmQoUEUzX2xvd2VyLDEpLCItIixyb3VuZChQRTNfdXBwZXIsMSksIikiKSwNCiAgICAgICAgIFBFX3JhbmdlND1wYXN0ZTAoIigiLHJvdW5kKFBFNF9sb3dlciwxKSwiLSIscm91bmQoUEU0X3VwcGVyLDEpLCIpIikpJT4lDQogIHNlbGVjdCh0YXhvbixzcGVjaW1lbix0b3RhbF9sZW5ndGgsDQogICAgICAgICBmaXQxLmZpdHRlZCxQRV9yYW5nZTEscmFuZ2UxLA0KICAgICAgICAgZml0Mi5maXR0ZWQsUEVfcmFuZ2UyLHJhbmdlMiwNCiAgICAgICAgIGZpdDMuZml0dGVkLFBFX3JhbmdlMyxyYW5nZTMsDQogICAgICAgICBmaXQ0LmZpdHRlZCxQRV9yYW5nZTQscmFuZ2U0KSU+JQ0KICBrYWJsZShkaWdpdHM9MSwNCiAgICAgICAgY2FwdGlvbj0gIkxlbmd0aCBlc3RpbWF0ZXMgZm9yIHNwZWNpbWVucyBvZiA8aT5EdW5rbGVvc3RldXMgdGVycmVsbGk8L2k+IHVzaW5nIGEgbW9kZWwgdGhhdCBpbmNvcnBvcmF0ZXMgcmVsYXRpdmUgYm9keSBkZXB0aC4gQWxsIGxlbmd0aHMgaW4gY20uIiwNCiAgICAgICAgY29sLm5hbWVzID0gYygiVGF4b24iLCJTcGVjaW1lbiIsIkFjdHVhbCBMZW5ndGgiLA0KICAgICAgICAgICAgICAgICAgICAgICJFc3QuIiwiKy8tICVQRSIsIjk1JSBQLkkuIiwNCiAgICAgICAgICAgICAgICAgICAgICAiRXN0LiIsIisvLSAlUEUiLCI5NSUgUC5JLiIsDQogICAgICAgICAgICAgICAgICAgICAgIkVzdC4iLCIrLy0gJVBFIiwiOTUlIFAuSS4iLA0KICAgICAgICAgICAgICAgICAgICAgICJFc3QuIiwiKy8tICVQRSIsIjk1JSBQLkkuIikpJT4lDQogIGFkZF9oZWFkZXJfYWJvdmUoYygiICI9MywiSW5jbHVkaW5nIGNvZWZmaWNpZW50IGFuZCBpbnRlcmFjdGlvbiBlZmZlY3QiPTMsIkludGVyYWN0aW9uIGVmZmVjdCB3aXRoIE9PTCBvbmx5Ij0zLCJCb2R5IGRlcHRoIGFzIGNvZWZmaWNpZW50IG9ubHkiPTMsDQogICAgICAgICAgICAgICAgICAgICAiSW50ZWdlciBvZiBoZWFkX2xlbmd0aC9ib2R5X2RlcHRoIj0zKSklPiUNCiAgY29sdW1uX3NwZWMoMSxpdGFsaWM9VCklPiUNCiAgY29sdW1uX3NwZWMoYyg0LDcsMTAsMTMpLGJvbGQ9VCklPiUNCiAga2FibGVfc3R5bGluZygpJT4lDQogIHNjcm9sbF9ib3god2lkdGggPSAiMTAwJSIpDQpgYGANCg0KIyBJbnZlc3RpZ2F0aW5nIGVmZmVjdCBvZiBzbm91dCBsZW5ndGggb24gcmVncmVzc2lvbiBlcXVhdGlvbiB7I3Nub3V0bGVuZ3RofQ0KDQojIyBTdGF0aXN0aWNzIG9mIG1vZGVsIGluY2x1ZGluZyBzbm91dCBsZW5ndGgNCg0KYGBge3J9DQp3aXRoX3Nub3V0X2xlbmd0aDwtbG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKHNub3V0X2xlbmd0aCkrbG9nKE9PTCksDQogICAgICAgICAgICAgICAgICAgICAgZGF0YT1kYXRhX2ZpbmFsKQ0Kc3VtbWFyeSh3aXRoX3Nub3V0X2xlbmd0aCkNCnJiaW5kKCJXaXRob3V0IFNub3V0IExlbmd0aCI9cmVncmVzc2lvbi5zdGF0cyhmaXQuT09MKSwNCiAgICAgICJXaXRoIFNub3V0IExlbmd0aCI9cmVncmVzc2lvbi5zdGF0cyh3aXRoX3Nub3V0X2xlbmd0aCkpDQpgYGANCg0KRnJvbSB0aGlzIGl0IGlzIHZlcnkgY2xlYXIgdGhhdCBhbW9uZyB0aGUgc2FtcGxlIG9mIGV4dGFudCBmaXNoZXMsIGluY2x1ZGluZyBzbm91dCBsZW5ndGggYXMgYW4gZXh0cmEgZXhwbGFuYXRvcnkgdGVybSBpbiB0aGUgbW9kZWwgc3VjaCB0aGF0IHByZS1vcmJpdGFsIGFuZCBPT0wgcmVnaW9ucyBvZiB0aGUgc2t1bGwgYXJlIGFsbG93ZWQgdG8gc2NhbGUgaW5kZXBlbmRlbnRseSBoYXMgYSBiZXR0ZXIgYWNjdXJhY3kgaW4gZXN0aW1hdGluZyB0b3RhbCBsZW5ndGggdGhhbiBqdXN0IHVzaW5nIE9PTCBieSBpdHNlbGYuDQoNCiMjIFByZWRpY3RpbmcgbGVuZ3RoIGluIGFydGhyb2RpcmVzIHVzaW5nIHNub3V0IGxlbmd0aA0KDQpgYGB7cn0NCmZvc3NpbF90YXhhJT4lDQogIGZpbHRlcighaXMubmEoc25vdXRfbGVuZ3RoKSwhIWZvc3NpbC5zcGVjaW1lbnMsY2xhZGU9PSJQbGFjb2Rlcm1pIiklPiUNCiAgbXV0YXRlKGVzdGltYXRlZF9sZW5ndGhfd2l0aF9zbm91dD1leHAocHJlZGljdCh3aXRoX3Nub3V0X2xlbmd0aCwuKSkqcmVncmVzc2lvbi5zdGF0cyh3aXRoX3Nub3V0X2xlbmd0aCkkQ0YsDQogICAgICAgICBlc3RpbWF0ZWRfbGVuZ3RoPWV4cChwcmVkaWN0KGZpdC5PT0wsLikpKnJlZ3Jlc3Npb24uc3RhdHMoZml0Lk9PTCkkQ0YpJT4lDQogIG11dGF0ZShQRV9zbm91dD0oZXN0aW1hdGVkX2xlbmd0aF93aXRoX3Nub3V0LXRvdGFsX2xlbmd0aCkvZXN0aW1hdGVkX2xlbmd0aF93aXRoX3Nub3V0LA0KICAgICAgICAgUEU9KGVzdGltYXRlZF9sZW5ndGgtdG90YWxfbGVuZ3RoKS9lc3RpbWF0ZWRfbGVuZ3RoKSU+JQ0KICByb3duYW1lc190b19jb2x1bW4oKSU+JQ0KICBzZWxlY3QodGF4b24sc3BlY2ltZW4sdG90YWxfbGVuZ3RoLGVzdGltYXRlZF9sZW5ndGgsUEUsZXN0aW1hdGVkX2xlbmd0aF93aXRoX3Nub3V0LFBFX3Nub3V0KSU+JQ0KICBrYWJsZShkaWdpdHM9YygxLDEsMSwxLDMsMSwzKSxhbGlnbj1jKCJsIiwiYyIsImMiLCJjIiwiYyIsImMiLCJjIiksDQogICAgICAgIGNvbC5uYW1lcyA9IGMoIlRheG9uIiwiU3BlY2ltZW4iLCJUb3RhbCBMZW5ndGgiLCJFc3RpbWF0ZWQgTGVuZ3RoIiwiJVBFIiwiRXN0aW1hdGVkIExlbmd0aCIsIiVQRSIpLA0KICAgICAgICBjYXB0aW9uPSJQcmVkaWN0aW5nIGxlbmd0aCBpbiB3aG9sZS1ib2R5IGFydGhyb2RpcmUgc3BlY2ltZW5zIHdpdGggT09MIGluY2x1ZGluZyBzbm91dCBsZW5ndGggYXMgYW4gYWRkaXRpb25hbCB2YXJpYWJsZSIpJT4lDQogIGFkZF9oZWFkZXJfYWJvdmUoYygiICI9MywiV2l0aG91dCBTbm91dCBMZW5ndGgiPTIsDQogICAgICAgICAgICAgICAgICAgICAiV2l0aCBTbm91dCBMZW5ndGgiPTIpKSU+JQ0KICBjb2x1bW5fc3BlYygxLGl0YWxpYz1UKSU+JQ0KICBjb2x1bW5fc3BlYyhjKDQsNiksYm9sZD1UKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQoocmVmOnNub3V0bGVuZ3RoKSBHcmFwaCBvZiBzbm91dCBsZW5ndGggdmVyc3VzIGhlYWQgbGVuZ3RoIChsZWZ0KSBhbmQgdG90YWwgbGVuZ3RoIChyaWdodCksIHNob3dpbmcgaG93IGFydGhyb2RpcmUgcGxhY29kZXJtcyBoYXZlIHNpZ25pZmljYW50bHkgc2hvcnRlciBzbm91dHMgdGhhbiBleHRhbnQgZmlzaGVzLiBCbGVubmlpZm9ybWVzIChpbiB3aGljaCBzbm91dCBsZW5ndGggaXMgbmVhciB6ZXJvKSBvbWl0dGVkIGZvciB2aXN1YWxpemF0aW9uIHB1cnBvc2VzLiBBbGwgYXhlcyBoZXJlIHVzaW5nIGEgbG9nPHN1Yj4xMDwvc3ViPiBzY2FsZS4NCg0KIyMgUmVsYXRpdmUgc25vdXQgbGVuZ3RoIHByb3BvcnRpb25zIGluIGZpc2hlcw0KDQpgYGB7cixmaWcud2lkdGg9MTAsZmlnLmFzcD0wLjUsZmlnLmNhcD0iKHJlZjpzbm91dGxlbmd0aCkiLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KZ3JpZC5hcnJhbmdlKG5yb3c9MSwNCmdncGxvdChkYXRhX2ZpbmFsJT4lDQogICAgICAgICBkcm9wX25hKGhlYWRfbGVuZ3RoLHNub3V0X2xlbmd0aCklPiUNCiAgICAgICAgIGZpbHRlcihjbGFkZSE9IlBldHJvbXl6b250aWZvcm1lcyIsDQogICAgICAgICAgICAgICAgb3JkZXIhPSJCbGVubmlpZm9ybWVzIiksDQogICAgICAgYWVzKHk9c25vdXRfbGVuZ3RoLHg9aGVhZF9sZW5ndGgpKSsNCiAgc2NhbGVfeF9jb250aW51b3VzKHRyYW5zPSJsb2cxMCIpKw0KICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnM9ImxvZzEwIikrDQogIGdlb21fc3RhcihhZXMoY29sb3I9Y2xhZGUsc3RhcnNoYXBlPWNsYWRlKSkrDQogIGdlb21fc3RhcihhZXMoZmlsbD1jbGFkZSxzdGFyc2hhcGU9Y2xhZGUpKSsNCiAgZ2VvbV9zbW9vdGgoZm9ybXVsYT15fngsbWV0aG9kPSJsbSIpKw0KICBnZW9tX3Ntb290aChkYXRhPS4lPiVmaWx0ZXIoY2xhZGU9PSJQbGFjb2Rlcm1pIiksYWVzKGNvbG9yPWNsYWRlKSwNCiAgICAgICAgICAgICAgZm9ybXVsYT15fngsbWV0aG9kPSJsbSIpKw0KICBsYWJzKHg9IkhlYWQgTGVuZ3RoIChtbSkiLHk9IlNub3V0IExlbmd0aCAobW0pIikrDQogIHNjYWxlX3N0YXJzaGFwZV9tYW51YWwodmFsdWVzPWMoMTUsMTUsMSwxNSkpKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoTkEsTkEsaHVlX3BhbCgpKDQpWzNdLE5BKSxuYS52YWx1ZT1OQSkrDQogIGdlb21fc3RhcihhZXMoZmlsbD1jbGFkZSxzdGFyc2hhcGU9Y2xhZGUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGE9LiU+JWZpbHRlcihjbGFkZT09IlBsYWNvZGVybWkiKSxzaXplPTIpKw0KICBnZ3RpdGxlKCJWZXJzdXMgSGVhZCBMZW5ndGgiKSsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiksDQpnZ3Bsb3QoZGF0YV9maW5hbCU+JQ0KICAgICAgICAgZHJvcF9uYSh0b3RhbF9sZW5ndGgsc25vdXRfbGVuZ3RoKSU+JQ0KICAgICAgICAgZmlsdGVyKCFjbGFkZSAlaW4lIGMoIlBldHJvbXl6b250aWZvcm1lcyIpLA0KICAgICAgICAgICAgICAgIG9yZGVyIT0iQmxlbm5paWZvcm1lcyIpLA0KICAgICAgIGFlcyh5PXNub3V0X2xlbmd0aCx4PXRvdGFsX2xlbmd0aCkpKw0KICBzY2FsZV94X2NvbnRpbnVvdXModHJhbnM9ImxvZzEwIikrDQogIHNjYWxlX3lfY29udGludW91cyh0cmFucz0ibG9nMTAiKSsNCiAgZ2VvbV9zdGFyKGFlcyhjb2xvcj1jbGFkZSxzdGFyc2hhcGU9Y2xhZGUpKSsNCiAgZ2VvbV9zdGFyKGFlcyhmaWxsPWNsYWRlLHN0YXJzaGFwZT1jbGFkZSkpKw0KICBnZW9tX3Ntb290aChmb3JtdWxhPXl+eCxtZXRob2Q9ImxtIikrDQogIHNjYWxlX3N0YXJzaGFwZV9tYW51YWwodmFsdWVzPWMoMTUsMTUsMSwxNSkpKw0KICBnZW9tX3Ntb290aChkYXRhPS4lPiVmaWx0ZXIoY2xhZGU9PSJQbGFjb2Rlcm1pIiksZm9ybXVsYT15fngsbWV0aG9kPSJsbSIsDQogICAgICAgICAgICAgIGFlcyhjb2xvcj1jbGFkZSkpKw0KICBnZW9tX3N0YXIoYWVzKGZpbGw9Y2xhZGUsc3RhcnNoYXBlPWNsYWRlKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhPS4lPiVmaWx0ZXIoY2xhZGU9PSJQbGFjb2Rlcm1pIiksc2l6ZT0yKSsNCiAgZ2d0aXRsZSgiVmVyc3VzIFRvdGFsIExlbmd0aCIpKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoTkEsTkEsaHVlX3BhbCgpKDQpWzNdLE5BKSxuYS52YWx1ZT1OQSkrDQogIGxhYnMoeD0iVG90YWwgTGVuZ3RoIChtbSkiLHk9IlNub3V0IExlbmd0aCAobW0pIixmaWxsPSJDbGFkZSIsDQogICAgICAgY29sb3I9IkNsYWRlIixzdGFyc2hhcGU9IkNsYWRlIikrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj1jKDAuODUsMC4yKSkpDQpgYGANCg0KKHJlZjpzbm91dGxlbmd0aHJlc2lkdWFscykgR3JhcGggb2YgdGhlIHJlc2lkdWFscyBvZiB0aGUgT09MIGVxdWF0aW9uIGFnYWluc3QgbG9nLXRyYW5zZm9ybWVkIHNub3V0IGxlbmd0aCwgc2hvd2luZyBob3cgc25vdXQgbGVuZ3RoIGhhcyBhIHNsaWdodCBhbGxvbWV0cmljIGNvcnJlbGF0aW9uIHdpdGggdG90YWwgbGVuZ3RoIGluIGZpc2hlcywgYnV0IHRoYXQgdGhlIHNob3J0IHNub3V0IG9mIGFydGhyb2RpcmVzIGRvZXMgbm90IGFwcGVhciB0byBzdHJvbmdseSBpbmZsdWVuY2UgdGhlaXIgcmVncmVzc2lvbiByZXN1bHRzLiBOb3RlIHRoYXQgQmxlbm5paWZvcm1lcyB3YXMgZXhjbHVkZWQgZHVlIHRvIHRoZSBmYWN0IHRoYXQgT09MIGFuZCBoZWFkIGxlbmd0aCBpbiB0aGVzZSB0YXhhIGFyZSBhbG1vc3QgZXF1YWwsIHdoaWNoIGNhdXNlcyBwcm9ibGVtcyB3aXRoIGxvZy10cmFuc2Zvcm1pbmcgc25vdXQtbGVuZ3RoIChpLmUuLCBgc25vdXRfbGVuZ3RoYD0wKS4NCg0KYGBge3IsZmlnLmNhcD0iKHJlZjpzbm91dGxlbmd0aHJlc2lkdWFscykifQ0KZ2dwbG90KGRhdGFfZmluYWwlPiUNCiAgICAgICAgIGZpbHRlcighaXMubmEodG90YWxfbGVuZ3RoKSAmICFpcy5uYShPT0wpLA0KICAgICAgICAgICAgICAgIGxlbmd0aF9hcyE9ImVzdGltYXRlZCB0LmwuIiklPiUNCiAgICAgICAgIG11dGF0ZShyZXNpZHVhbHM9Zml0Lk9PTCRyZXNpZHVhbHMpJT4lDQogICAgICAgICBmaWx0ZXIob3JkZXIhPSJCbGVubmlpZm9ybWVzIiksDQogICAjTm90ZTogQmxlbm5paWZvcm1lcyB3YXMgZXhjbHVkZWQgZm9yIGNsYXJpdHkgYmVjYXVzZSBtYW55IGJsZW5uaWVzIGhhdmUgZXllcyB0aGF0IGFyZSBhdCB0aGUgdmVyeSBhbnRlcmlvciBlbmQgb2YgdGhlaXIgc2t1bGwsIHRodXMgaGVhZF9sZW5ndGg9PXBvc3RvcmJpdGFsIGxlbmd0aCBvciBpcyB2ZXJ5IGNsb3NlLCBhbmQgd2hlbiBsb2ctdHJhbnNmb3JtZWQgdGhpcyBjYXVzZXMgaXNzdWVzIHdpdGggcGxvdHRpbmcgdGhlIHJlc3VsdHMuDQogICAgICAgYWVzKHg9c25vdXRfbGVuZ3RoLHk9cmVzaWR1YWxzKSkrDQogIHNjYWxlX3hfY29udGludW91cyh0cmFucz0ibG9nMTAiKSsNCiAgZ2VvbV9zdGFyKGFlcyhmaWxsPWNsYWRlLHN0YXJzaGFwZT1jbGFkZSkpKw0KICBnZW9tX3N0YXIoZGF0YT0uJT4lZmlsdGVyKGNsYWRlPT0iUGxhY29kZXJtaSIpLA0KICAgICAgICAgICAgIGFlcyhzdGFyc2hhcGU9Y2xhZGUpLGZpbGw9IndoaXRlIixjb2xvcj0id2hpdGUiLHNpemU9MyxzaG93LmxlZ2VuZD1GKSsNCiAgZ2VvbV9zdGFyKGRhdGE9LiU+JWZpbHRlcihjbGFkZT09IlBsYWNvZGVybWkiKSxzaXplPTIuNSwNCiAgICAgICAgICAgICBhZXMoZmlsbD1jbGFkZSxzdGFyc2hhcGU9Y2xhZGUpLHNob3cubGVnZW5kPUYpKw0KICBzY2FsZV9zdGFyc2hhcGVfbWFudWFsKHZhbHVlcz1jKDE1LDEzLDExLDEsMjgpKSsNCiAgZ2VvbV9zbW9vdGgoZm9ybXVsYT15fngsbWV0aG9kPSJsbSIpKw0KICBsYWJzKHg9IlNub3V0IExlbmd0aCAobW0pIix5PSJSZXNpZHVhbHMgb2YgT09MIEVxdWF0aW9uIixjb2xvcj0iQ2xhZGUiLGZpbGw9IkNsYWRlIixzdGFyc2hhcGU9IkNsYWRlIikrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj1jKDAuMTUsMC44KSkNCmBgYA0KDQojIyMgU2NhdHRlciBwbG90IG9mIHBlcmNlbnQgc25vdXQgbGVuZ3RoIHZlcnN1cyBoZWFkIGxlbmd0aA0KDQoocmVmOnBlcmNlbnRzbm91dGxlbmd0aCkgU2NhdHRlciBwbG90ICgqKkEqKikgYW5kIGJveC1hbmQtd2hpc2tlciBwbG90ICgqKkIqKikgb2Ygc25vdXQgbGVuZ3RoIGFzIGEgcGVyY2VudGFnZSBvZiBoZWFkIGxlbmd0aCBpbiBmaXNoZXMsIHNob3dpbmcgaG93IGFydGhyb2RpcmUgcGxhY29kZXJtcyBoYXZlIHNpZ25pZmljYW50bHkgc2hvcnRlciBzbm91dHMgdGhhbiBvdGhlciBncm91cHMuDQoNCmBgYHtyLGZpZy53aWR0aD0xMCxmaWcuYXNwPTAuNSxmaWcuY2FwPSIocmVmOnBlcmNlbnRzbm91dGxlbmd0aCkiLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KZ3JpZC5hcnJhbmdlKG5yb3c9MSwNCiAgZ2dwbG90KGRhdGFfZmluYWwlPiVmaWx0ZXIoIWlzLm5hKHNub3V0X2xlbmd0aCksIWlzLm5hKGhlYWRfbGVuZ3RoKSksDQogICAgICAgYWVzKHg9aGVhZF9sZW5ndGgsDQogICAgICAgICAgIHk9c25vdXRfbGVuZ3RoL2hlYWRfbGVuZ3RoKSkrDQogICAgZ2d0aXRsZSgiQSIpKw0KICAgIGdlb21fc3RhcihhZXMoZmlsbD1jbGFkZSksLiU+JWZpbHRlcihjbGFkZSE9IlBsYWNvZGVybWkiKSxzdGFyc2hhcGU9MTUpKw0KICAgIGdlb21fc21vb3RoKGZvcm11bGE9eX54LG1ldGhvZD0ibG0iLGFlcyhjb2xvcj1jbGFkZSksYWxwaGE9MC4yNSkrDQogICAgc2NhbGVfeF9jb250aW51b3VzKHRyYW5zPSJsb2cxMCIpKw0KICAgIGdlb21fc3RhcihkYXRhPS4lPiVmaWx0ZXIoY2xhZGU9PSJQbGFjb2Rlcm1pIiksZmlsbD0id2hpdGUiLGNvbG9yPSJ3aGl0ZSIsc2l6ZT0zKSsNCiAgICBnZW9tX3N0YXIoZGF0YT0uJT4lZmlsdGVyKGNsYWRlPT0iUGxhY29kZXJtaSIpLGFlcyhmaWxsPWNsYWRlKSxzaXplPTIuNSkrDQogICAgbGFicyhjb2xvcj0iQ2xhZGUiLHk9IlBlcmNlbnQgU25vdXQgTGVuZ3RoIix4PSJIZWFkIExlbmd0aCAoY20pIikrDQogICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscz1zY2FsZXM6OmxhYmVsX3BlcmNlbnQoYWNjdXJhY3kgPSAxKSkrDQogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiksDQogIGdncGxvdChkYXRhX2ZpbmFsJT4lZmlsdGVyKCFpcy5uYShzbm91dF9sZW5ndGgpLCFpcy5uYShoZWFkX2xlbmd0aCkpLA0KICAgICAgIGFlcyh4PWNsYWRlLA0KICAgICAgICAgICB5PXNub3V0X2xlbmd0aC9oZWFkX2xlbmd0aCkpKw0KICAgICAgZ2d0aXRsZSgiQiIpKw0KICAgIGdlb21faGxpbmUoYWVzKHlpbnRlcmNlcHQ9bWVhbihzbm91dF9sZW5ndGgvaGVhZF9sZW5ndGgpKSxsaW5ldHlwZT0iZGFzaGVkIikrDQogICAgZ2VvbV92aW9saW4oc2NhbGU9IndpZHRoIixhZXMoZmlsbD1jbGFkZSkpKw0KICAgIGdlb21fYm94cGxvdCh3aWR0aD0wLjQpKw0KICAgIGxhYnMoZmlsbD0iQ2xhZGUiLHk9IlNub3V0IExlbmd0aCBBcyAlIEhlYWQgTGVuZ3RoIix4PSJDbGFkZSIpKw0KICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHM9c2NhbGVzOjpsYWJlbF9wZXJjZW50KGFjY3VyYWN5ID0gMSkpKw0KICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj1jKDAuOCwwLjgpLA0KICAgICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCB2anVzdCA9IDEsIGhqdXN0PTEpKSkNCmBgYA0KDQojIyBQZXJjZW50IHNub3V0IGxlbmd0aCB3aXRoIHNwZWNpZXMtYXZlcmFnZSB2YWx1ZXMNCg0KYGBge3IsY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZSd9DQpkYXRhX2ZpbmFsJT4lDQogIGRyb3BfbmEoc25vdXRfbGVuZ3RoLGhlYWRfbGVuZ3RoKSU+JQ0KICBncm91cF9ieSh0YXhvbiklPiUNCiAgc3VtbWFyaXNlKHNub3V0X2xlbmd0aD1tZWFuKHNub3V0X2xlbmd0aCksDQogICAgICAgICAgICBoZWFkX2xlbmd0aD1tZWFuKGhlYWRfbGVuZ3RoKSwNCiAgICAgICAgICAgIGNsYWRlPXVuaXF1ZShjbGFkZSkpJT4lDQogIGdncGxvdChhZXMoeD1oZWFkX2xlbmd0aCwNCiAgICAgICAgICAgICB5PXNub3V0X2xlbmd0aC9oZWFkX2xlbmd0aCkpKw0KICBnZW9tX3N0YXIoYWVzKGZpbGw9Y2xhZGUsc3RhcnNoYXBlPWNsYWRlKSkrDQogIHNjYWxlX3N0YXJzaGFwZV9tYW51YWwodmFsdWVzPWMoMTUsMTUsMTUsMSwxNSkpKw0KICBnZW9tX3Ntb290aChmb3JtdWxhPXl+eCxtZXRob2Q9ImxtIixhZXMoY29sb3I9Y2xhZGUpLGFscGhhPTAuMjUpKw0KICBnZW9tX3N0YXIoYWVzKGZpbGw9Y2xhZGUsc3RhcnNoYXBlPWNsYWRlKSwNCiAgICAgICAgICAgIC4lPiVmaWx0ZXIoY2xhZGU9PSJQbGFjb2Rlcm1pIiksc2l6ZT0yLjUpKw0KICBzY2FsZV94X2NvbnRpbnVvdXModHJhbnM9ImxvZzEwIikrDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHM9c2NhbGVzOjpsYWJlbF9wZXJjZW50KGFjY3VyYWN5ID0gMSkpKw0KICBsYWJzKGNvbG9yPSJDbGFkZSIseT0iUGVyY2VudCBTbm91dCBMZW5ndGgiLHg9IkhlYWQgTGVuZ3RoIChjbSkiKQ0KYGBgDQoNCiMjIE1vZGVsIGluY2x1ZGluZyBzbm91dCBsZW5ndGgsIHdpdGggdmFyaWFibGUgY2xhZGUgbGV2ZWwNCg0KYGBge3J9DQpkYXRhX2ZpbmFsJT4lDQogIGRyb3BfbmEodG90YWxfbGVuZ3RoLHNub3V0X2xlbmd0aCxPT0wpJT4lDQogIGdyb3VwX2J5KHRheG9uKSU+JQ0KICBzdW1tYXJpc2UoYWNyb3NzKGModG90YWxfbGVuZ3RoLE9PTCxzbm91dF9sZW5ndGgpLH5tZWFuKC4pKSwNCiAgICAgICAgICAgIGNsYWRlPXVuaXF1ZShjbGFkZSkpJSQlDQogIGxtKGxvZyh0b3RhbF9sZW5ndGgpfmxvZyhPT0wpK2xvZyhzbm91dF9sZW5ndGgpKmNsYWRlLWNsYWRlKSU+JQ0KICBzdW1tYXJ5KCkNCmBgYA0KDQoqKkFOT1ZBIGV4YW1pbmluZyBpZiBzbm91dCBwcm9wb3J0aW9ucyAoYXMgYSBwZXJjZW50YWdlIG9mIHRvdGFsIGxlbmd0aCkgYXJlIGNvbnNpc3RlbnQgYWNyb3NzIGZpc2hlcyoqDQoNCmBgYHtyfQ0KZGF0YV9maW5hbCU+JQ0KICBkcm9wX25hKHRvdGFsX2xlbmd0aCxzbm91dF9sZW5ndGgpJT4lDQogIGdyb3VwX2J5KHRheG9uKSU+JQ0KICBzdW1tYXJpc2UoYWNyb3NzKGModG90YWxfbGVuZ3RoLHNub3V0X2xlbmd0aCksfm1lYW4oLikpLA0KICAgICAgICAgICAgY2xhZGU9dW5pcXVlKGNsYWRlKSklPiUNCiAgbXV0YXRlKHBlcmNlbnRfc25vdXQ9c25vdXRfbGVuZ3RoL3RvdGFsX2xlbmd0aCklPiUNCiAgbG0ocGVyY2VudF9zbm91dH5jbGFkZSwuKSAlPiUgc3VtbWFyeSgpDQpgYGANCg0KKipBTk9WQSBleGFtaW5pbmcgaWYgc25vdXQgcHJvcG9ydGlvbnMgKGFzIGEgcGVyY2VudGFnZSBvZiBoZWFkIGxlbmd0aCkgYXJlIGNvbnNpc3RlbnQgYWNyb3NzIGZpc2hlcyoqDQoNCmBgYHtyfQ0KZGF0YV9maW5hbCU+JQ0KICBkcm9wX25hKGhlYWRfbGVuZ3RoLHNub3V0X2xlbmd0aCklPiUNCiAgZ3JvdXBfYnkodGF4b24pJT4lDQogIHN1bW1hcmlzZShhY3Jvc3MoYyhoZWFkX2xlbmd0aCxzbm91dF9sZW5ndGgpLH5tZWFuKC4pKSwNCiAgICAgICAgICAgIGNsYWRlPXVuaXF1ZShjbGFkZSkpJT4lDQogIG11dGF0ZShwZXJjZW50X3Nub3V0PXNub3V0X2xlbmd0aC9oZWFkX2xlbmd0aCklPiUNCiAgbG0ocGVyY2VudF9zbm91dH5jbGFkZSwuKSAlPiUgc3VtbWFyeSgpDQpgYGANCg0KQmFzZWQgb24gdGhpcywgaXQgYXBwZWFycyB0aGF0IGFydGhyb2RpcmVzLCBvbiBhdmVyYWdlLCBoYXZlIHNob3J0ZXIgc25vdXRzIChwcmVvcmJpdGFsIGxlbmd0aCkgdGhhbiBjaG9uZHJpY2h0aHlhbnMsIGFjdGlub3B0ZXJ5Z2lhbnMsIGxhbXByZXlzLCBhbmQgc2FyY29wdGVyeWdpYW5zLiBOb3RlIHRoZSBtYWduaXR1ZGUgb2YgdGhlIGludGVyYWN0aW9uIGNvZWZmaWNpZW50IGZvciBhcnRocm9kaXJlIGlzIG11Y2ggZ3JlYXRlciB0aGFuIGFsbCBvdGhlciBmaXNoIGNsYWRlcy4NCg0KYGBge3J9DQpmaXQuc25vdXRfd2l0aF9jbGFkZTwtbG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCkrbG9nKHNub3V0X2xlbmd0aCkqY2xhZGUtY2xhZGUsDQogICAgICAgICAgICAgICAgICAgICAgICAgZGF0YV9maW5hbCkNCmZpdC5zbm91dF93aXRoX2NsYWRlMjwtbG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCkrbG9nKHNub3V0X2xlbmd0aCkqY2xhZGUsDQogICAgICAgICAgICAgICAgICAgICAgICAgZGF0YV9maW5hbCkNCnN1bW1hcnkoZml0LnNub3V0X3dpdGhfY2xhZGUpDQpgYGANCg0KYGBge3J9DQpyYmluZCgiV2l0aG91dCBTbm91dCBMZW5ndGgiPXJlZ3Jlc3Npb24uc3RhdHMoZml0Lk9PTCksDQogICAgICAiV2l0aCBTbm91dCBMZW5ndGgiPXJlZ3Jlc3Npb24uc3RhdHMod2l0aF9zbm91dF9sZW5ndGgpLA0KICAgICAgIldpdGggU25vdXQgTGVuZ3RoIGFuZCBJbnRlcmFjdGlvbiBFZmZlY3QgZnJvbSBDbGFkZSI9cmVncmVzc2lvbi5zdGF0cyhmaXQuc25vdXRfd2l0aF9jbGFkZSksDQogICAgICAiV2l0aCBDbGFkZSBhcyBCb3RoIEludGVyYWN0aW9uIEZhY3RvciBhbmQgVmFyaWFibGUiPXJlZ3Jlc3Npb24uc3RhdHMoZml0LnNub3V0X3dpdGhfY2xhZGUyKSkNCmBgYA0KDQpUaGUgZXF1YXRpb24gaGVyZSBpcyB3cml0dGVuIGFzLi4uDQoNCiQkDQpcdGV4dHtsbShsb2codG90YWxfbGVuZ3RoKX5sb2coT09MKStsb2coc25vdXRfbGVuZ3RoKSpjbGFkZS1jbGFkZX0NCiQkDQoNClRoaXMgaXMgbmVjZXNzYXJ5IGJlY2F1c2UgdGhlIHZhcmlhYmxlIG9mIGludGVyZXN0IGlzIHRoZSAqaW50ZXJhY3Rpb24qIGJldHdlZW4gc25vdXQgbGVuZ3RoIGFuZCBjbGFkZSBtZW1iZXJzaGlwLiBJLmUuLCB0aGUgZXF1YXRpb24gbmVlZHMgdG8gY29tcGVuc2F0ZSBmb3IgdGhlIGZhY3QgdGhhdCBhcnRocm9kaXJlcyBoYXZlIHNob3J0ZXIgc25vdXRzIHRoYW4gb3RoZXIgZmlzaGVzLiBJZiB0aGUgdGVybSAnLWNsYWRlJyBpcyBub3QgaW5jbHVkZWQsIHRoZW4gYGNsYWRlYCBieSBpdHNlbGYgd2lsbCBiZSBjb25zaWRlcmVkIGFzIGFuIGFkZGl0aW9uYWwgZXhwbGFuYXRvcnkgdmFyaWFibGUgaW4gdGhlIGFuYWx5c2lzLg0KDQpgYGB7cn0NCnN1bW1hcnkoZml0LnNub3V0X3dpdGhfY2xhZGUyKQ0KYGBgDQoNCkFzIHNlZW4gaGVyZSwgdGhlIGludGVyYWN0aW9uIGJldHdlZW4gYHNub3V0IGxlbmd0aGAgYW5kIGBjbGFkZWAgaXMgbm8gbG9uZ2VyIHNpZ25pZmljYW50LCBidXQgZ2VuZXJhbCBjbGFkZSBtZW1iZXJzaGlwIGluIFBsYWNvZGVybWkgaXMuIFRoaXMgbWVhbnMgdGhlIG1vZGVsIGlzIG5vdCBhY2NvdW50aW5nIGZvciB2YXJpYXRpb24gZHJpdmVuIGJ5IHRoZSBzaG9ydGVyIHNub3V0cyBvZiBhcnRocm9kaXJlcywgYnV0IGlzIHBvbGFyaXplZCBieSBnZW5lcmFsIHJlc2lkdWFsIHZhcmlhdGlvbiBiZXR3ZWVuIG1lYXN1cmVkIGFydGhyb2RpcmVzIGFuZCBvdGhlciBmaXNoZXMgKGV4Y2VwdCBmb3Igc25vdXQgbGVuZ3RoLCBzaW5jZSB0aGF0IGlzIHJlcHJlc2VudGVkIGJ5IHRoZSB0ZXJtIGBsb2coc25vdXRfbGVuZ3RoKTpjbGFkZVBsYWNvZGVybWlgKS4gVGhpcyByZXNpZHVhbCB2YXJpYXRpb24gbWF5IG5vdCBjaGFyYWN0ZXJpemUgKkR1bmtsZW9zdGV1cyosIGVzcGVjaWFsbHkgYmVjYXVzZSBpdCBpcyBsYXJnZWx5IGRyaXZlbiBieSBwYXR0ZXJuIGluIGNvY2Nvc3Rlb21vcnBocy4NCg0KSG93ZXZlciwgZm9yIHRoZSBzYWtlIG9mIHRyYW5zcGFyZW5jeSwgdGhlIHJlc3VsdHMgb2YgYm90aCBhcmUgcHJlc2VudGVkIGhlcmUuDQoNCmBgYHtyfQ0KZm9zc2lsX3RheGElPiUNCiAgZmlsdGVyKGxlbmd0aF9hcz09InRvdGFsIGxlbmd0aCJ8c3BlY2ltZW4gJWluJSBjKCJDTU5IIDU3NjgiLCJDTU5IIDc0MjQiLCJDTU5IIDYwOTAiLCJDTU5IIDcwNTQiKSklPiUNCiAgYXVnbWVudChmaXQuc25vdXRfd2l0aF9jbGFkZSxuZXdkYXRhPS4saW50ZXJ2YWw9InByZWRpY3Rpb24iKSU+JQ0KICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnLicpKSwgZnVucyhwYXN0ZTAoImZpdDEiLC4pKSklPiUNCiAgYXVnbWVudChmaXQuc25vdXRfd2l0aF9jbGFkZTIsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0aW9uIiklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCJmaXQyIiwuKSkpJT4lDQogIG11dGF0ZShhY3Jvc3MoZml0MS5maXR0ZWQ6Zml0MS51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0LnNub3V0X3dpdGhfY2xhZGUpJENGKSwNCiAgICAgICAgIGFjcm9zcyhmaXQyLmZpdHRlZDpmaXQyLnVwcGVyLH5leHAoLikqcmVncmVzc2lvbi5zdGF0cyhmaXQuc25vdXRfd2l0aF9jbGFkZTIpJENGKSklPiUNCiAgbXV0YXRlKFBFMV9sb3dlcj1maXQxLmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQucmVsYXRpdmVkZXB0aDEpJGFkalBFKS8xMDApLA0KICAgICAgICAgUEUxX3VwcGVyPWZpdDEuZml0dGVkKigxKyhyZWdyZXNzaW9uLnN0YXRzKGZpdC5yZWxhdGl2ZWRlcHRoMSkkYWRqUEUpLzEwMCksDQogICAgICAgICBQRTJfbG93ZXI9Zml0Mi5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnJlbGF0aXZlZGVwdGgyKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIFBFMl91cHBlcj1maXQyLmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQucmVsYXRpdmVkZXB0aDIpJGFkalBFKS8xMDApLA0KICAgICAgICAgcmFuZ2UxPXBhc3RlMCgiKCIscm91bmQoZml0MS5sb3dlciwxKSwiLSIscm91bmQoZml0MS51cHBlciwxKSwiKSIpLA0KICAgICAgICAgcmFuZ2UyPXBhc3RlMCgiKCIscm91bmQoZml0Mi5sb3dlciwxKSwiLSIscm91bmQoZml0Mi51cHBlciwxKSwiKSIpLA0KICAgICAgICAgUEVfcmFuZ2UxPXBhc3RlMCgiKCIscm91bmQoUEUxX2xvd2VyLDEpLCItIixyb3VuZChQRTFfdXBwZXIsMSksIikiKSwNCiAgICAgICAgIFBFX3JhbmdlMj1wYXN0ZTAoIigiLHJvdW5kKFBFMl9sb3dlciwxKSwiLSIscm91bmQoUEUyX3VwcGVyLDEpLCIpIikpJT4lDQogIHNlbGVjdCh0YXhvbixzcGVjaW1lbix0b3RhbF9sZW5ndGgsZml0MS5maXR0ZWQsUEVfcmFuZ2UxLHJhbmdlMSwNCiAgICAgICAgIGZpdDIuZml0dGVkLFBFX3JhbmdlMixyYW5nZTIpJT4lDQogIGFycmFuZ2UoZml0MS5maXR0ZWQpJT4lDQogIGthYmxlKGRpZ2l0cz0xLGFsaWduPWMoImwiLCJjIiwiYyIsImMiLCJjIiwiYyIsImMiLCJjIiwiYyIpLA0KICAgICAgICBjYXB0aW9uPSAiTGVuZ3RoIGVzdGltYXRlcyBmb3Igc3BlY2ltZW5zIG9mIDxpPkR1bmtsZW9zdGV1cyB0ZXJyZWxsaTwvaT4gdXNpbmcgYSBtb2RlbCB0aGF0IGluY2x1ZGVzIHNub3V0IGxlbmd0aCBidXQgYWxsb3dzIGZvciBzbm91dCBsZW5ndGggdG8gdmFyeSBieSBjbGFkZSIsDQogICAgICAgIGNvbC5uYW1lcyA9IGMoIlRheG9uIiwiU3BlY2ltZW4iLCJBY3R1YWwgTGVuZ3RoIiwiRXN0LiIsIisvLSAlUEUuIiwiOTUlIFAuSS4iLA0KICAgICAgICAgICAgICAgICAgICAgICJFc3QuIiwiKy8tICVQRSIsIjk1JSBQLkkuIikpJT4lDQogIGNvbHVtbl9zcGVjKDEsIGl0YWxpYyA9IFQpJT4lDQogIGNvbHVtbl9zcGVjKGMoMyw0LDcpLCBib2xkID0gVCklPiUNCiAgYWRkX2hlYWRlcl9hYm92ZShjKCIgIj0zLCJNb2RlbCB3aXRoIGNvdmFyeWluZyB0ZXJtIGZvciBjbGFkZSBvbmx5Ij0zLCJNb2RlbCB3aXRoIGV4dHJhIHRlcm0gZm9yIGNsYWRlIj0zKSklPiUNCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KQWxzbyBub3RlIHRoYXQgZGVzcGl0ZSBpbmNsdWRpbmcgdGhlIGV4dHJhIHRlcm0sIHNvbWUgb2YgdGhlIGxlbmd0aHMgYXJlIHZlcnkgZmFyIG9mZiBmcm9tIHRoZWlyIGFjdHVhbCB2YWx1ZXMuIEFub3RoZXIgaXNzdWUgaXMgdGhhdCBieSBhZGRpbmcgYSB0ZXJtIGJhc2VkIG9uIGFsbG9tZXRyaWMgcmVsYXRpb25zaGlwcyB3aXRoaW4gQXJ0aHJvZGlyYSwgdGhpcyBpcyBlZmZlY3RpdmVseSB1bmR1bHkgaW5mbHVlbmNpbmcgdGhlIGxlbmd0aCBlc3RpbWF0ZXMgaW4gKkR1bmtsZW9zdGV1cyogYnkgcGF0dGVybnMgaW4gYSBzbWFsbCBudW1iZXIgb2YgbXVjaCBzbWFsbGVyIHRheGEsIHdoaWNoIG1heSBub3QgbmVjZXNzYXJpbHkgYmUgY29ycmVjdCAoc2VlIGRpc2N1c3Npb24gaW4gW0ZlcnLDs24gZXQgYWwuLCAyMDE3XSgjcmVmZXJlbmNlcykpLiBUaGlzIGlzIHRoZSBlbnRpcmUgcHJvYmxlbSB0aGF0IHRoZSB1c2Ugb2YgT09MIHNldCBvdXQgdG8gYXZvaWQgaW4gdGhlIGZpcnN0IHBsYWNlLiBUaHVzLCBgc25vdXRfbGVuZ3RoYCBkb2VzIG5vdCBhcHBlYXIgdG8gYmUgYSBmZWFzaWJsZSB2YXJpYWJsZSB0byBpbmNsdWRlIGluIHRoZSBtb2RlbCBhdCB0aGlzIHRpbWUuDQoNCiMgQ29tcGFyaW5nIGxlbmd0aCBlc3RpbWF0ZXMgYWNyb3NzIG1vZGVscyB1c2luZyAqQ29jY29zdGV1cyBjdXNwaWRhdHVzKiBhbmQgQ01OSCA1NzY4ICgqRHVua2xlb3N0ZXVzIHRlcnJlbGxpKikNCg0KQ29kZSBmb3IgZml0dGluZyBzYW1lIG1vZGVscyBhcyBhYm92ZSwgYnV0IGZvciBzcGVjaWVzLWF2ZXJhZ2VzLg0KDQpgYGB7cixjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJyxjYWNoZS5leHRyYSA9IHRvb2xzOjptZDVzdW0oIkRldm9uaWFuIEZpc2ggVGFsZSBTdXBwbGVtZW50YXJ5IEZpbGUgMSAoRGF0YSkueGxzeCIpfQ0KcHJlZGljdC5zcGVjaWVzX2F2ZXJhZ2VzMjwtKHByZWRpY3QoZml0LnNwZWNpZXNfYXZlcmFnZTIsZm9zc2lsX3RheGEgJT4lDQogICAgICAgICAgIGRyb3BfbmEoT09MLGJvZHlfd2lkdGgsYm9keV9kZXB0aCksDQogICAgICAgICBpbnRlcnZhbD0icHJlZGljdGlvbiIpJT4lDQogIGV4cCgpKnJlZ3Jlc3Npb24uc3RhdHMoZml0LnNwZWNpZXNfYXZlcmFnZTIpJENGKSU+JQ0KICBkYXRhLmZyYW1lKCklPiVyb3duYW1lc190b19jb2x1bW4oKSU+JQ0KICBhcnJhbmdlKGZpdCklPiUNCiAgbGVmdF9qb2luKGZvc3NpbF90YXhhJT4lcm93bmFtZXNfdG9fY29sdW1uKCksYnk9InJvd25hbWUiKSU+JQ0KICBmaWx0ZXIoISFmb3NzaWwuc3BlY2ltZW5zKSU+JQ0KICBzZWxlY3QodGF4b24sc3BlY2ltZW4sZml0LGx3cix1cHIpDQoNCnByZWRpY3Quc3BlY2llc19hdmVyYWdlczwtKHByZWRpY3QoZml0LnNwZWNpZXNfYXZlcmFnZSxmb3NzaWxfdGF4YSAlPiUNCiAgICAgICAgICAgZmlsdGVyKCFpcy5uYShPT0wpLCFpcy5uYShib2R5X3dpZHRoKSwhaXMubmEoYm9keV9kZXB0aCkpLA0KICAgICAgICAgaW50ZXJ2YWw9InByZWRpY3Rpb24iKSU+JQ0KICBleHAoKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5zcGVjaWVzX2F2ZXJhZ2UpJENGKSU+JQ0KICBkYXRhLmZyYW1lKCklPiVyb3duYW1lc190b19jb2x1bW4oKSU+JQ0KICBhcnJhbmdlKGZpdCklPiUNCiAgbGVmdF9qb2luKGZvc3NpbF90YXhhJT4lcm93bmFtZXNfdG9fY29sdW1uKCksYnk9InJvd25hbWUiKSU+JQ0KICBmaWx0ZXIoISFmb3NzaWwuc3BlY2ltZW5zKSU+JQ0KICBzZWxlY3QodGF4b24sc3BlY2ltZW4sZml0LGx3cix1cHIpDQoNCiMgUmVncmVzc2lvbiBlcXVhdGlvbiBmb3Igc2hhcmstb25seSBtb2RlbCB1c2luZyBzcGVjaWVzIGF2ZXJhZ2VzDQpmaXQuc2hhcmtzX2F2ZXJhZ2VzPC1sbShsb2codG90YWxfbGVuZ3RoKX5sb2coT09MKSwNCiAgICAgICAgICAgICAgICBkYXRhX3NwZWNpZXMlPiUNCiAgICAgICAgICAgICAgICAgZmlsdGVyKGNsYWRlPT0iQ2hvbmRyaWNodGh5ZXMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIW9yZGVyICVpbiUgYygiUmFqaWZvcm1lcyIsIkNoaW1hZXJpZm9ybWVzIiksDQogICAgICAgICAgICAgICAgICAgICAgICBnZW51cyE9IkFsb3BpYXMiKSkNCg0KcHJlZGljdC5zaGFya3NfYXZlcmFnZXM8LShwcmVkaWN0KGZpdC5zaGFya3NfYXZlcmFnZXMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3NzaWxfdGF4YSAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHJvcF9uYShPT0wsYm9keV93aWR0aCxib2R5X2RlcHRoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGludGVydmFsPSJwcmVkaWN0aW9uIiklPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV4cCgpKnJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXJrc19hdmVyYWdlcykkQ0YpJT4lDQogIGRhdGEuZnJhbWUoKSU+JXJvd25hbWVzX3RvX2NvbHVtbigpJT4lDQogIGFycmFuZ2UoZml0KSU+JQ0KICBsZWZ0X2pvaW4oZm9zc2lsX3RheGElPiVyb3duYW1lc190b19jb2x1bW4oKSxieT0icm93bmFtZSIpJT4lDQogIGZpbHRlcighIWZvc3NpbC5zcGVjaW1lbnMpJT4lDQogIHNlbGVjdCh0YXhvbixzcGVjaW1lbixmaXQsbHdyLHVwcikNCg0KIyBSZWdyZXNzaW9uIGVxdWF0aW9uIGZvciBmdXNpZm9ybSB0YXhhIHVzaW5nIHNwZWNpZXMgYXZlcmFnZXMNCmZpdC5mdXNpZm9ybV9hdmVyYWdlczwtbG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCksDQogICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfc3BlY2llcyU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihzaGFwZSAlaW4lIGMoImZ1c2lmb3JtIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFmYW1pbHkgJWluJSBjKCJTZXJyYW5pZGFlIiwgIk1vbmFjYW50aGlkYWUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiSG9sb2NlbnRyaWRhZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJCYWxpc3RpZGFlIikpKQ0KDQojIE1vZGVsIGluY2x1ZGluZyBzaGFwZSwgdXNpbmcgc3BlY2llcyBhdmVyYWdlcw0KZml0LndpdGhfc2hhcGVfYXZlcmFnZXM8LWxtKGxvZyh0b3RhbF9sZW5ndGgpfmxvZyhPT0wpK3NoYXBlKyhmYW1pbHk9PSJTZXJyYW5pZGFlIikrKGZhbWlseT09IkhvbG9jZW50cmlkYWUiKSwNCiAgICAgICAgICAgICAgICAgICBkYXRhX3NwZWNpZXMlPiVmaWx0ZXIoIWZhbWlseSAlaW4lIGMoIkJhbGlzdGlkYWUiLCJNb25hY2FudGhpZGFlIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFnZW51cyAlaW4lIGMoIlJlZ2FsZWN1cyIsIlJoaW5vY2hpbWFlcmEiKSkpDQoNCnByZWRpY3Qud2l0aF9zaGFwZV9hdmVyYWdlczwtKHByZWRpY3QoZml0LndpdGhfc2hhcGVfYXZlcmFnZXMsZm9zc2lsX3RheGEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGludGVydmFsPSJwcmVkaWN0aW9uIiklPiUNCiAgICAgICAgICAgICAgICAgICAgICAgZXhwKCkqcmVncmVzc2lvbi5zdGF0cyhmaXQud2l0aF9zaGFwZSkkQ0YpJT4lDQogIGRhdGEuZnJhbWUoKSU+JQ0KICByb3duYW1lc190b19jb2x1bW4oKSU+JQ0KICBhcnJhbmdlKGZpdCklPiUNCiAgbGVmdF9qb2luKGZvc3NpbF90YXhhJT4lcm93bmFtZXNfdG9fY29sdW1uKCksYnk9InJvd25hbWUiKSU+JQ0KICBmaWx0ZXIoISFmb3NzaWwuc3BlY2ltZW5zKSU+JQ0KICBzZWxlY3QodGF4b24sc3BlY2ltZW4sZml0LGx3cix1cHIpDQoNCiMgTW9kZWwgaW5jbHVkaW5nIHNoYXBlIGFuZCB2YXJpYWJsZSBzbG9wZSBmb3IgQ2hvbmRyaWNodGh5ZXMsIHVzaW5nIHNwZWNpZXMgYXZlcmFnZXMNCmZpdC5zaGFwZV9jbGFkZTQ8LWxtKGxvZyh0b3RhbF9sZW5ndGgpfmxvZyhPT0wpKihjbGFkZT09IkNob25kcmljaHRoeWVzIikrDQogICAgICAgICAgICAgICAgICAgICAgIHNoYXBlKyhmYW1pbHk9PSJTZXJyYW5pZGFlIikrKGZhbWlseT09IkhvbG9jZW50cmlkYWUiKSwNCiAgICAgICAgICAgICAgICAgICAgZGF0YV9zcGVjaWVzKQ0KDQojIE1vZGVsIGluY2x1ZGluZyBib2R5IGhlaWdodCBhcyBhIGNvdmFyaWF0ZQ0KZml0LnJlbGF0aXZlZGVwdGhfYXZlcmFnZXM8LQ0KICBsbShsb2codG90YWxfbGVuZ3RoKX5sb2coT09MKStJKGxvZyhoZWFkX2xlbmd0aCkvbG9nKGJvZHlfZGVwdGgpKSwNCiAgICAgZGF0YV9maW5hbCU+JQ0KICAgICAgIGRyb3BfbmEodG90YWxfbGVuZ3RoLE9PTCxib2R5X2RlcHRoLGhlYWRfbGVuZ3RoKSU+JQ0KICAgICAgIGdyb3VwX2J5KHRheG9uKSU+JQ0KICAgICAgIHN1bW1hcmlzZShhY3Jvc3MoYyh0b3RhbF9sZW5ndGgsaGVhZF9sZW5ndGgsT09MLGJvZHlfZGVwdGgpLG1lYW4pLA0KICAgICAgICAgICAgICAgICBhY3Jvc3MoYyhjbGFkZSxoYWJpdGF0LHNoYXBlKSx1bmlxdWUpKQ0KICAgICApDQoNCiMgRWxvbmdhdGUgYW5kIGZ1c2lmb3JtIG5vbi1hY2FudGhvcHRlcnlnaWFucyBvbmx5LCBzcGVjaWVzIGF2ZXJhZ2VzDQpmaXQubm9fYWNhbnRob3B0ZXJ5Z2lpMl9hdmVyYWdlczwtZGF0YV9zcGVjaWVzJT4lDQogIGZpbHRlcihoaWdoZXJfZ3JvdXAhPSJBY2FudGhvcHRlcnlnaWkiLA0KICAgICAgICAgc2hhcGUgJWluJSBjKCJlbG9uZ2F0ZSIsImZ1c2lmb3JtIikpJSQlDQogIGxtKGxvZyh0b3RhbF9sZW5ndGgpfmxvZyhPT0wpLC4pDQoNCiMgVXNpbmcgaGVhZCBsZW5ndGgsIGluZGl2aWR1YWwgZGF0YQ0KZml0LmhlYWRfbGVuZ3RoPC1sbShsb2codG90YWxfbGVuZ3RoKX5sb2coaGVhZF9sZW5ndGgpLGRhdGFfZmluYWwpDQojIFVzaW5nIGhlYWQgbGVuZ3RoLCBzcGVjZXNfYXZlcmFnZXMNCmZpdC5oZWFkX2xlbmd0aF9hdmVyYWdlczwtbG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKGhlYWRfbGVuZ3RoKSxkYXRhX3NwZWNpZXMpDQpgYGANCg0KQ3JlYXRpbmcgdGFibGUNCg0KYGBge3IsY2FjaGUuZXh0cmEgPSB0b29sczo6bWQ1c3VtKCJEZXZvbmlhbiBGaXNoIFRhbGUgU3VwcGxlbWVudGFyeSBGaWxlIDEgKERhdGEpLnhsc3giKX0NCkNNTkg1NzY4X2xlbmd0aHM8LWZvc3NpbF90YXhhJT4lDQogIGZpbHRlcihzcGVjaW1lbiAlaW4lIGMoIkNNTkggNTc2OCIsIlJlY29uLiAoTSAmIFcgMTk2OCkiKSklPiUNCiAgc2VsZWN0KGdlbnVzOmxlbmd0aF9hcyklPiUNCiAgYXVnbWVudChmaXQuT09MLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdCIpJT4lDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSoNCiAgICAgICAgICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoZml0Lk9PTCkkQ0YpLA0KICAgICAgICAgLlBFX2xvd2VyPS5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0Lk9PTCkkYWRqUEUpLzEwMCksDQogICAgICAgICAuUEVfdXBwZXI9LmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQuT09MKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIC5QRT1wYXN0ZTAoIigiLA0KICAgICAgICAgICAgICAgICAgICAuZml0dGVkKigxLShyZWdyZXNzaW9uLnN0YXRzKGZpdC5PT0wpJGFkalBFKS8xMDApLA0KICAgICAgICAgICAgICAgICAgICAiLSIsDQogICAgICAgICAgICAgICAgICAgIC5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMoZml0Lk9PTCkkYWRqUEUpLzEwMCksDQogICAgICAgICAgICAgICAgICAgICIpIikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgnQWxsIFNwZWNpZXMuaW5kaXZpZHVhbF9kYXRhJywgLikpKSU+JQ0KICBhdWdtZW50KGZpdC5zcGVjaWVzX2F2ZXJhZ2UsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0IiklPiUNCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKg0KICAgICAgICAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQuc3BlY2llc19hdmVyYWdlKSRDRiksDQogICAgICAgICAuUEVfbG93ZXI9LmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQuc3BlY2llc19hdmVyYWdlKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIC5QRV91cHBlcj0uZml0dGVkKigxKyhyZWdyZXNzaW9uLnN0YXRzKGZpdC5zcGVjaWVzX2F2ZXJhZ2UpJGFkalBFKS8xMDApLA0KICAgICAgICAgLlBFPXBhc3RlMCgiKCIsDQogICAgICAgICAgICAgICAgICAgIC5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnNwZWNpZXNfYXZlcmFnZSkkYWRqUEUpLzEwMCksDQogICAgICAgICAgICAgICAgICAgICItIiwNCiAgICAgICAgICAgICAgICAgICAgLmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQuc3BlY2llc19hdmVyYWdlKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgICAgICAgICAgICAgIikiKSklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCdBbGwgU3BlY2llcy5zcGVjaWVzX2F2ZXJhZ2VzJywgLikpKSU+JQ0KIyBFeGNsdWRpbmcgdW51c3VhbCBib2R5IHNoYXBlcw0KIyMgSW5kaXZpZHVhbCBkYXRhDQogIGF1Z21lbnQoZml0Lm5vX2V4dHJlbWVfc2hhcGVzLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdCIpJT4lDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSoNCiAgICAgICAgICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoZml0Lm5vX2V4dHJlbWVfc2hhcGVzKSRDRiksDQogICAgICAgICAuUEVfbG93ZXI9LmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQubm9fZXh0cmVtZV9zaGFwZXMpJGFkalBFKS8xMDApLA0KICAgICAgICAgLlBFX3VwcGVyPS5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMoZml0Lm5vX2V4dHJlbWVfc2hhcGVzKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIC5QRT1wYXN0ZTAoIigiLA0KICAgICAgICAgICAgICAgICAgICAuZml0dGVkKigxLShyZWdyZXNzaW9uLnN0YXRzKGZpdC5ub19leHRyZW1lX3NoYXBlcykkYWRqUEUpLzEwMCksDQogICAgICAgICAgICAgICAgICAgICItIiwNCiAgICAgICAgICAgICAgICAgICAgLmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQubm9fZXh0cmVtZV9zaGFwZXMpJGFkalBFKS8xMDApLA0KICAgICAgICAgICAgICAgICAgICAiKSIpKSU+JQ0KICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnLicpKSwgZnVucyhwYXN0ZTAoJ05vIEV4dHJlbWUgU2hhcGVzLmluZGl2aWR1YWxfZGF0YScsIC4pKSklPiUNCiMjIFNwZWNpZXMgYXZlcmFnZXMNCiAgYXVnbWVudChmaXQuc3BlY2llc19hdmVyYWdlMixuZXdkYXRhPS4saW50ZXJ2YWw9InByZWRpY3QiKSU+JQ0KICBtdXRhdGUoYWNyb3NzKC5maXR0ZWQ6LnVwcGVyLH5leHAoLikqDQogICAgICAgICAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKGZpdC5zcGVjaWVzX2F2ZXJhZ2UyKSRDRiksDQogICAgICAgICAuUEVfbG93ZXI9LmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQuc3BlY2llc19hdmVyYWdlMikkYWRqUEUpLzEwMCksDQogICAgICAgICAuUEVfdXBwZXI9LmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQuc3BlY2llc19hdmVyYWdlMikkYWRqUEUpLzEwMCksDQogICAgICAgICAuUEU9cGFzdGUwKCIoIiwNCiAgICAgICAgICAgICAgICAgICAgLmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQuc3BlY2llc19hdmVyYWdlMikkYWRqUEUpLzEwMCksDQogICAgICAgICAgICAgICAgICAgICItIiwNCiAgICAgICAgICAgICAgICAgICAgLmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQuc3BlY2llc19hdmVyYWdlMikkYWRqUEUpLzEwMCksDQogICAgICAgICAgICAgICAgICAgICIpIikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgnTm8gRXh0cmVtZSBTaGFwZXMuc3BlY2llc19hdmVyYWdlcycsIC4pKSklPiUNCiMgV2l0aCBzaGFwZSBhcyBhIGNvdmFyaWF0ZQ0KIyMgSW5kaXZpZHVhbCBkYXRhDQogIGF1Z21lbnQoZml0LndpdGhfc2hhcGUsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0IiklPiUNCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKg0KICAgICAgICAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQud2l0aF9zaGFwZSkkQ0YpLA0KICAgICAgICAgLlBFX2xvd2VyPS5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LndpdGhfc2hhcGUpJGFkalBFKS8xMDApLA0KICAgICAgICAgLlBFX3VwcGVyPS5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMoZml0LndpdGhfc2hhcGUpJGFkalBFKS8xMDApLA0KICAgICAgICAgLlBFPXBhc3RlMCgiKCIsDQogICAgICAgICAgICAgICAgICAgIC5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LndpdGhfc2hhcGUpJGFkalBFKS8xMDApLA0KICAgICAgICAgICAgICAgICAgICAiLSIsDQogICAgICAgICAgICAgICAgICAgIC5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMoZml0LndpdGhfc2hhcGUpJGFkalBFKS8xMDApLA0KICAgICAgICAgICAgICAgICAgICAiKSIpKSU+JQ0KICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnLicpKSwgZnVucyhwYXN0ZTAoJ1dpdGggU2hhcGUgYXMgYSBDb3ZhcmlhdGUuaW5kaXZpZHVhbF9kYXRhJywgLikpKSU+JQ0KIyMgU3BlY2llcyBhdmVyYWdlcw0KICBhdWdtZW50KGZpdC53aXRoX3NoYXBlX2F2ZXJhZ2VzLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdCIpJT4lDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSoNCiAgICAgICAgICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoZml0LndpdGhfc2hhcGVfYXZlcmFnZXMpJENGKSwNCiAgICAgICAgIC5QRV9sb3dlcj0uZml0dGVkKigxLShyZWdyZXNzaW9uLnN0YXRzKGZpdC53aXRoX3NoYXBlX2F2ZXJhZ2VzKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIC5QRV91cHBlcj0uZml0dGVkKigxKyhyZWdyZXNzaW9uLnN0YXRzKGZpdC53aXRoX3NoYXBlX2F2ZXJhZ2VzKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIC5QRT1wYXN0ZTAoIigiLA0KICAgICAgICAgICAgICAgICAgICAuZml0dGVkKigxLShyZWdyZXNzaW9uLnN0YXRzKGZpdC53aXRoX3NoYXBlX2F2ZXJhZ2VzKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgICAgICAgICAgICAgIi0iLA0KICAgICAgICAgICAgICAgICAgICAuZml0dGVkKigxKyhyZWdyZXNzaW9uLnN0YXRzKGZpdC53aXRoX3NoYXBlX2F2ZXJhZ2VzKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgICAgICAgICAgICAgIikiKSklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCdXaXRoIFNoYXBlIGFzIGEgQ292YXJpYXRlLnNwZWNpZXNfYXZlcmFnZXMnLCAuKSkpJT4lDQojIEZ1c2lmb3JtIHRheGEgb25seQ0KIyMgRnVzaWZvcm0gdGF4YSBvbmx5LCBpbmRpdmlkdWFsIGRhdGENCiAgYXVnbWVudChmaXQuZnVzaWZvcm0sbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0IiklPiUNCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKg0KICAgICAgICAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQuZnVzaWZvcm0pJENGKSwNCiAgICAgICAgIC5QRV9sb3dlcj0uZml0dGVkKigxLShyZWdyZXNzaW9uLnN0YXRzKGZpdC5mdXNpZm9ybSkkYWRqUEUpLzEwMCksDQogICAgICAgICAuUEVfdXBwZXI9LmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQuZnVzaWZvcm0pJGFkalBFKS8xMDApLA0KICAgICAgICAgLlBFPXBhc3RlMCgiKCIsDQogICAgICAgICAgICAgICAgICAgIC5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LmZ1c2lmb3JtKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgICAgICAgICAgICAgIi0iLA0KICAgICAgICAgICAgICAgICAgICAuZml0dGVkKigxKyhyZWdyZXNzaW9uLnN0YXRzKGZpdC5mdXNpZm9ybSkkYWRqUEUpLzEwMCksDQogICAgICAgICAgICAgICAgICAgICIpIikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgnRnVzaWZvcm0gVGF4YSBPbmx5LmluZGl2aWR1YWxfZGF0YScsIC4pKSklPiUNCiMjIEZ1c2lmb3JtIHRheGEgb25seSwgc3BlY2llcyBhdmVyYWdlcw0KICBhdWdtZW50KGZpdC5mdXNpZm9ybV9hdmVyYWdlcyxuZXdkYXRhPS4saW50ZXJ2YWw9InByZWRpY3QiKSU+JQ0KICBtdXRhdGUoYWNyb3NzKC5maXR0ZWQ6LnVwcGVyLH5leHAoLikqDQogICAgICAgICAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKGZpdC5mdXNpZm9ybV9hdmVyYWdlcykkQ0YpLA0KICAgICAgICAgLlBFX2xvd2VyPS5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LmZ1c2lmb3JtX2F2ZXJhZ2VzKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIC5QRV91cHBlcj0uZml0dGVkKigxKyhyZWdyZXNzaW9uLnN0YXRzKGZpdC5mdXNpZm9ybV9hdmVyYWdlcykkYWRqUEUpLzEwMCksDQogICAgICAgICAuUEU9cGFzdGUwKCIoIiwNCiAgICAgICAgICAgICAgICAgICAgLmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQuZnVzaWZvcm1fYXZlcmFnZXMpJGFkalBFKS8xMDApLA0KICAgICAgICAgICAgICAgICAgICAiLSIsDQogICAgICAgICAgICAgICAgICAgIC5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMoZml0LmZ1c2lmb3JtX2F2ZXJhZ2VzKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgICAgICAgICAgICAgIikiKSklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCdGdXNpZm9ybSBUYXhhIE9ubHkuc3BlY2llc19hdmVyYWdlcycsIC4pKSklPiUNCiMgSW5jbHVkaW5nIGJvZHkgaGVpZ2h0IGFzIGEgY292YXJpYXRlDQojIyBJbmRpdmlkdWFsIGRhdGENCiAgYXVnbWVudChmaXQucmVsYXRpdmVkZXB0aDQsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0IiklPiUNCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKg0KICAgICAgICAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQucmVsYXRpdmVkZXB0aDQpJENGKSwNCiAgICAgICAgIC5QRV9sb3dlcj0uZml0dGVkKigxLShyZWdyZXNzaW9uLnN0YXRzKGZpdC5yZWxhdGl2ZWRlcHRoNCkkYWRqUEUpLzEwMCksDQogICAgICAgICAuUEVfdXBwZXI9LmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQucmVsYXRpdmVkZXB0aDQpJGFkalBFKS8xMDApLA0KICAgICAgICAgLlBFPXBhc3RlMCgiKCIsDQogICAgICAgICAgICAgICAgICAgIC5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnJlbGF0aXZlZGVwdGg0KSRhZGpQRSkvMTAwKSwNCiAgICAgICAgICAgICAgICAgICAgIi0iLA0KICAgICAgICAgICAgICAgICAgICAuZml0dGVkKigxKyhyZWdyZXNzaW9uLnN0YXRzKGZpdC5yZWxhdGl2ZWRlcHRoNCkkYWRqUEUpLzEwMCksDQogICAgICAgICAgICAgICAgICAgICIpIikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgnV2l0aCBCb2R5IEhlaWdodCBhcyBDb3ZhcmlhdGUuaW5kaXZpZHVhbF9kYXRhJywgLikpKSU+JQ0KIyMgU3BlY2llcyBhdmVyYWdlcw0KICBhdWdtZW50KGZpdC5yZWxhdGl2ZWRlcHRoX2F2ZXJhZ2VzLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdCIpJT4lDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSoNCiAgICAgICAgICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoZml0LnJlbGF0aXZlZGVwdGhfYXZlcmFnZXMpJENGK3Nub3V0X2xlbmd0aCksDQogICAgICAgICAuUEVfbG93ZXI9LmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQucmVsYXRpdmVkZXB0aF9hdmVyYWdlcykkYWRqUEUpLzEwMCksDQogICAgICAgICAuUEVfdXBwZXI9LmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQucmVsYXRpdmVkZXB0aF9hdmVyYWdlcykkYWRqUEUpLzEwMCksDQogICAgICAgICAuUEU9cGFzdGUwKCIoIiwNCiAgICAgICAgICAgICAgICAgICAgLmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQucmVsYXRpdmVkZXB0aF9hdmVyYWdlcykkYWRqUEUpLzEwMCksDQogICAgICAgICAgICAgICAgICAgICItIiwNCiAgICAgICAgICAgICAgICAgICAgLmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQucmVsYXRpdmVkZXB0aF9hdmVyYWdlcykkYWRqUEUpLzEwMCksDQogICAgICAgICAgICAgICAgICAgICIpIikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgnV2l0aCBCb2R5IEhlaWdodCBhcyBDb3ZhcmlhdGUuc3BlY2llc19hdmVyYWdlcycsIC4pKSklPiUNCiMgVHJlYXRpbmcgc25vdXQgbGVuZ3RoIGFzIGFuIGludGVnZXINCiMjIEluZGl2aWR1YWwgZGF0YQ0KICBhdWdtZW50KGZpdC5taW51c19zbm91dF9sZW5ndGgxLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdCIpJT4lDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSoNCiAgICAgICAgICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoZml0Lm1pbnVzX3Nub3V0X2xlbmd0aDEpJENGK3Nub3V0X2xlbmd0aCksDQogICAgICAgICAuUEVfbG93ZXI9LmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQubWludXNfc25vdXRfbGVuZ3RoMSkkYWRqUEUpLzEwMCksDQogICAgICAgICAuUEVfdXBwZXI9LmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQubWludXNfc25vdXRfbGVuZ3RoMSkkYWRqUEUpLzEwMCksDQogICAgICAgICAuUEU9cGFzdGUwKCIoIiwNCiAgICAgICAgICAgICAgICAgICAgLmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQubWludXNfc25vdXRfbGVuZ3RoMSkkYWRqUEUpLzEwMCksDQogICAgICAgICAgICAgICAgICAgICItIiwNCiAgICAgICAgICAgICAgICAgICAgLmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQubWludXNfc25vdXRfbGVuZ3RoMSkkYWRqUEUpLzEwMCksDQogICAgICAgICAgICAgICAgICAgICIpIikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgnVHJlYXRpbmcgU25vdXQgTGVuZ3RoIGFzIFNlcGFyYXRlIEludGVnZXIuaW5kaXZpZHVhbF9kYXRhJywgLikpKSU+JQ0KIyMgU3BlY2llcyBhdmVyYWdlcw0KICBhdWdtZW50KGZpdC5taW51c19zbm91dF9sZW5ndGgyLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdCIpJT4lDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSoNCiAgICAgICAgICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoZml0Lm1pbnVzX3Nub3V0X2xlbmd0aDIpJENGK3Nub3V0X2xlbmd0aCksDQogICAgICAgICAuUEVfbG93ZXI9LmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQubWludXNfc25vdXRfbGVuZ3RoMikkYWRqUEUpLzEwMCksDQogICAgICAgICAuUEVfdXBwZXI9LmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQubWludXNfc25vdXRfbGVuZ3RoMikkYWRqUEUpLzEwMCksDQogICAgICAgICAuUEU9cGFzdGUwKCIoIiwNCiAgICAgICAgICAgICAgICAgICAgLmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQubWludXNfc25vdXRfbGVuZ3RoMikkYWRqUEUpLzEwMCksDQogICAgICAgICAgICAgICAgICAgICItIiwNCiAgICAgICAgICAgICAgICAgICAgLmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQubWludXNfc25vdXRfbGVuZ3RoMikkYWRqUEUpLzEwMCksDQogICAgICAgICAgICAgICAgICAgICIpIikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgnVHJlYXRpbmcgU25vdXQgTGVuZ3RoIGFzIFNlcGFyYXRlIEludGVnZXIuc3BlY2llc19hdmVyYWdlcycsIC4pKSklPiUNCiMgUGVsYWdpYyB0YXhhIG9ubHkNCiMjIEluZGl2aWR1YWwgZGF0YQ0KICBhdWdtZW50KGZpdC5wZWxhZ2ljLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdCIpJT4lDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSoNCiAgICAgICAgICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoZml0LnBlbGFnaWMpJENGKSwNCiAgICAgICAgIC5QRV9sb3dlcj0uZml0dGVkKigxLShyZWdyZXNzaW9uLnN0YXRzKGZpdC5wZWxhZ2ljKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIC5QRV91cHBlcj0uZml0dGVkKigxKyhyZWdyZXNzaW9uLnN0YXRzKGZpdC5wZWxhZ2ljKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIC5QRT1wYXN0ZTAoIigiLA0KICAgICAgICAgICAgICAgICAgICAuZml0dGVkKigxLShyZWdyZXNzaW9uLnN0YXRzKGZpdC5wZWxhZ2ljKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgICAgICAgICAgICAgIi0iLA0KICAgICAgICAgICAgICAgICAgICAuZml0dGVkKigxKyhyZWdyZXNzaW9uLnN0YXRzKGZpdC5wZWxhZ2ljKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgICAgICAgICAgICAgIikiKSklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCdQZWxhZ2ljIFRheGEgT25seS5pbmRpdmlkdWFsX2RhdGEnLCAuKSkpJT4lDQojIyBTcGVjaWVzIGF2ZXJhZ2VzDQogIGF1Z21lbnQoZml0LnBlbGFnaWNfYXZlcmFnZXMsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0IiklPiUNCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKg0KICAgICAgICAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQucGVsYWdpY19hdmVyYWdlcykkQ0YpLA0KICAgICAgICAgLlBFX2xvd2VyPS5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnBlbGFnaWNfYXZlcmFnZXMpJGFkalBFKS8xMDApLA0KICAgICAgICAgLlBFX3VwcGVyPS5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnBlbGFnaWNfYXZlcmFnZXMpJGFkalBFKS8xMDApLA0KICAgICAgICAgLlBFPXBhc3RlMCgiKCIsDQogICAgICAgICAgICAgICAgICAgIC5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnBlbGFnaWNfYXZlcmFnZXMpJGFkalBFKS8xMDApLA0KICAgICAgICAgICAgICAgICAgICAiLSIsDQogICAgICAgICAgICAgICAgICAgIC5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnBlbGFnaWNfYXZlcmFnZXMpJGFkalBFKS8xMDApLA0KICAgICAgICAgICAgICAgICAgICAiKSIpKSU+JQ0KICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnLicpKSwgZnVucyhwYXN0ZTAoJ1BlbGFnaWMgVGF4YSBPbmx5LnNwZWNpZXNfYXZlcmFnZXMnLCAuKSkpJT4lDQojIEV4Y2x1ZGluZyBhY2FudGhvcHRlcnlnaWFuIHRheGEgb25seQ0KIyMgSW5kaXZpZHVhbCBkYXRhDQogIGF1Z21lbnQoZml0Lm5vX2FjYW50aG9wdGVyeWdpaTIsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0IiklPiUNCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKg0KICAgICAgICAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQubm9fYWNhbnRob3B0ZXJ5Z2lpMikkQ0YpLA0KICAgICAgICAgLlBFX2xvd2VyPS5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0Lm5vX2FjYW50aG9wdGVyeWdpaTIpJGFkalBFKS8xMDApLA0KICAgICAgICAgLlBFX3VwcGVyPS5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMoZml0Lm5vX2FjYW50aG9wdGVyeWdpaTIpJGFkalBFKS8xMDApLA0KICAgICAgICAgLlBFPXBhc3RlMCgiKCIsDQogICAgICAgICAgICAgICAgICAgIC5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0Lm5vX2FjYW50aG9wdGVyeWdpaTIpJGFkalBFKS8xMDApLA0KICAgICAgICAgICAgICAgICAgICAiLSIsDQogICAgICAgICAgICAgICAgICAgIC5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMoZml0Lm5vX2FjYW50aG9wdGVyeWdpaTIpJGFkalBFKS8xMDApLA0KICAgICAgICAgICAgICAgICAgICAiKSIpKSU+JQ0KICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnLicpKSwgZnVucyhwYXN0ZTAoJ05vbi1BY2FudGhvcHRlcnlnaWFucyBPbmx5LmluZGl2aWR1YWxfZGF0YScsIC4pKSklPiUNCiMjIFNwZWNpZXMgYXZlcmFnZXMNCiAgYXVnbWVudChmaXQubm9fYWNhbnRob3B0ZXJ5Z2lpMl9hdmVyYWdlcyxuZXdkYXRhPS4saW50ZXJ2YWw9InByZWRpY3QiKSU+JQ0KICBtdXRhdGUoYWNyb3NzKC5maXR0ZWQ6LnVwcGVyLH5leHAoLikqDQogICAgICAgICAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKGZpdC5ub19hY2FudGhvcHRlcnlnaWkyX2F2ZXJhZ2VzKSRDRiksDQogICAgICAgICAuUEVfbG93ZXI9LmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQubm9fYWNhbnRob3B0ZXJ5Z2lpMl9hdmVyYWdlcykkYWRqUEUpLzEwMCksDQogICAgICAgICAuUEVfdXBwZXI9LmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQubm9fYWNhbnRob3B0ZXJ5Z2lpMl9hdmVyYWdlcykkYWRqUEUpLzEwMCksDQogICAgICAgICAuUEU9cGFzdGUwKCIoIiwNCiAgICAgICAgICAgICAgICAgICAgLmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQubm9fYWNhbnRob3B0ZXJ5Z2lpMl9hdmVyYWdlcykkYWRqUEUpLzEwMCksDQogICAgICAgICAgICAgICAgICAgICItIiwNCiAgICAgICAgICAgICAgICAgICAgLmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQubm9fYWNhbnRob3B0ZXJ5Z2lpMl9hdmVyYWdlcykkYWRqUEUpLzEwMCksDQogICAgICAgICAgICAgICAgICAgICIpIikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgnTm9uLUFjYW50aG9wdGVyeWdpYW5zIE9ubHkuc3BlY2llc19hdmVyYWdlcycsIC4pKSklPiUNCiMgU2hhcmsgdGF4YSBvbmx5DQojIyBJbmRpdmlkdWFsIGRhdGENCiAgYXVnbWVudChmaXQuc2hhcmtzLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdCIpJT4lDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSoNCiAgICAgICAgICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXJrcykkQ0YpLA0KICAgICAgICAgLlBFX2xvd2VyPS5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXJrcykkYWRqUEUpLzEwMCksDQogICAgICAgICAuUEVfdXBwZXI9LmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQuc2hhcmtzKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIC5QRT1wYXN0ZTAoIigiLA0KICAgICAgICAgICAgICAgICAgICAuZml0dGVkKigxLShyZWdyZXNzaW9uLnN0YXRzKGZpdC5zaGFya3MpJGFkalBFKS8xMDApLA0KICAgICAgICAgICAgICAgICAgICAiLSIsDQogICAgICAgICAgICAgICAgICAgIC5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXJrcykkYWRqUEUpLzEwMCksDQogICAgICAgICAgICAgICAgICAgICIpIikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgnU2hhcmtzIE9ubHkuaW5kaXZpZHVhbF9kYXRhJywgLikpKSU+JQ0KIyMgU3BlY2llcyBhdmVyYWdlcw0KICBhdWdtZW50KGZpdC5zaGFya3NfYXZlcmFnZXMsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0IiklPiUNCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKg0KICAgICAgICAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQuc2hhcmtzX2F2ZXJhZ2VzKSRDRiksDQogICAgICAgICAuUEVfbG93ZXI9LmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQuc2hhcmtzX2F2ZXJhZ2VzKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIC5QRV91cHBlcj0uZml0dGVkKigxKyhyZWdyZXNzaW9uLnN0YXRzKGZpdC5zaGFya3NfYXZlcmFnZXMpJGFkalBFKS8xMDApLA0KICAgICAgICAgLlBFPXBhc3RlMCgiKCIsDQogICAgICAgICAgICAgICAgICAgIC5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXJrc19hdmVyYWdlcykkYWRqUEUpLzEwMCksDQogICAgICAgICAgICAgICAgICAgICItIiwNCiAgICAgICAgICAgICAgICAgICAgLmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQuc2hhcmtzX2F2ZXJhZ2VzKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgICAgICAgICAgICAgIikiKSklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCdTaGFya3MgT25seS5zcGVjaWVzX2F2ZXJhZ2VzJywgLikpKSU+JQ0KIyBXaXRoIHNoYXBlIGFzIGEgY292YXJpYXRlIGFuZCB2YXJpYWJsZSBzbG9wZXMgZm9yIENob25kcmljaHRoeWVzIGFuZCBhbGwgb3RoZXIgZmlzaGVzDQojIyBJbmRpdmlkdWFsIGRhdGENCiAgYXVnbWVudChmaXQuc2hhcGVfY2xhZGUzLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdCIpJT4lDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSoNCiAgICAgICAgICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlMykkQ0YpLA0KICAgICAgICAgLlBFX2xvd2VyPS5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlMykkYWRqUEUpLzEwMCksDQogICAgICAgICAuUEVfdXBwZXI9LmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQuc2hhcGVfY2xhZGUzKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIC5QRT1wYXN0ZTAoIigiLA0KICAgICAgICAgICAgICAgICAgICAuZml0dGVkKigxLShyZWdyZXNzaW9uLnN0YXRzKGZpdC5zaGFwZV9jbGFkZTMpJGFkalBFKS8xMDApLA0KICAgICAgICAgICAgICAgICAgICAiLSIsDQogICAgICAgICAgICAgICAgICAgIC5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlMykkYWRqUEUpLzEwMCksDQogICAgICAgICAgICAgICAgICAgICIpIikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgnQm9keSBTaGFwZSBhbmQgVmFyaWFibGUgU2xvcGUgZm9yIENob25kcmljaHRoeWVzLmluZGl2aWR1YWxfZGF0YScsIC4pKSklPiUNCiMjIFNwZWNpZXMgYXZlcmFnZXMNCiAgYXVnbWVudChmaXQuc2hhcGVfY2xhZGU0LG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdCIpJT4lDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSoNCiAgICAgICAgICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlNCkkQ0YpLA0KICAgICAgICAgLlBFX2xvd2VyPS5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlNCkkYWRqUEUpLzEwMCksDQogICAgICAgICAuUEVfdXBwZXI9LmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQuc2hhcGVfY2xhZGU0KSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIC5QRT1wYXN0ZTAoIigiLA0KICAgICAgICAgICAgICAgICAgICAuZml0dGVkKigxLShyZWdyZXNzaW9uLnN0YXRzKGZpdC5zaGFwZV9jbGFkZTQpJGFkalBFKS8xMDApLA0KICAgICAgICAgICAgICAgICAgICAiLSIsDQogICAgICAgICAgICAgICAgICAgIC5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlNCkkYWRqUEUpLzEwMCksDQogICAgICAgICAgICAgICAgICAgICIpIikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgnQm9keSBTaGFwZSBhbmQgVmFyaWFibGUgU2xvcGUgZm9yIENob25kcmljaHRoeWVzLnNwZWNpZXNfYXZlcmFnZXMnLCAuKSkpJT4lDQojIFVzaW5nIGhlYWQgbGVuZ3RoDQojIyBJbmRpdmlkdWFsIGRhdGENCiAgYXVnbWVudChmaXQuaGVhZF9sZW5ndGgsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0IiklPiUNCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKg0KICAgICAgICAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQuaGVhZF9sZW5ndGgpJENGKSwNCiAgICAgICAgIC5QRV9sb3dlcj0uZml0dGVkKigxLShyZWdyZXNzaW9uLnN0YXRzKGZpdC5oZWFkX2xlbmd0aCkkYWRqUEUpLzEwMCksDQogICAgICAgICAuUEVfdXBwZXI9LmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQuaGVhZF9sZW5ndGgpJGFkalBFKS8xMDApLA0KICAgICAgICAgLlBFPXBhc3RlMCgiKCIsDQogICAgICAgICAgICAgICAgICAgIC5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LmhlYWRfbGVuZ3RoKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgICAgICAgICAgICAgIi0iLA0KICAgICAgICAgICAgICAgICAgICAuZml0dGVkKigxKyhyZWdyZXNzaW9uLnN0YXRzKGZpdC5oZWFkX2xlbmd0aCkkYWRqUEUpLzEwMCksDQogICAgICAgICAgICAgICAgICAgICIpIikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgnSGVhZCBMZW5ndGguaW5kaXZpZHVhbF9kYXRhJywgLikpKSU+JQ0KIyMgU3BlY2llcyBhdmVyYWdlcw0KICBhdWdtZW50KGZpdC5oZWFkX2xlbmd0aF9hdmVyYWdlcyxuZXdkYXRhPS4saW50ZXJ2YWw9InByZWRpY3QiKSU+JQ0KICBtdXRhdGUoYWNyb3NzKC5maXR0ZWQ6LnVwcGVyLH5leHAoLikqDQogICAgICAgICAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKGZpdC5oZWFkX2xlbmd0aF9hdmVyYWdlcykkQ0YpLA0KICAgICAgICAgLlBFX2xvd2VyPS5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LmhlYWRfbGVuZ3RoX2F2ZXJhZ2VzKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIC5QRV91cHBlcj0uZml0dGVkKigxKyhyZWdyZXNzaW9uLnN0YXRzKGZpdC5oZWFkX2xlbmd0aF9hdmVyYWdlcykkYWRqUEUpLzEwMCksDQogICAgICAgICAuUEU9cGFzdGUwKCIoIiwNCiAgICAgICAgICAgICAgICAgICAgLmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQuaGVhZF9sZW5ndGhfYXZlcmFnZXMpJGFkalBFKS8xMDApLA0KICAgICAgICAgICAgICAgICAgICAiLSIsDQogICAgICAgICAgICAgICAgICAgIC5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMoZml0LmhlYWRfbGVuZ3RoX2F2ZXJhZ2VzKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgICAgICAgICAgICAgIikiKSklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCdIZWFkIExlbmd0aC5zcGVjaWVzX2F2ZXJhZ2VzJywgLikpKSU+JQ0KICBzZWxlY3QoZ2VudXMsDQogICAgICAgICAiQWxsIFNwZWNpZXMuaW5kaXZpZHVhbF9kYXRhLmZpdHRlZCI6bGFzdF9jb2woKSwNCiAgICAgICAgIC1lbmRzX3dpdGgoYygicm93bmFtZXMiLCJyZXNpZCIsIlBFIikpKSU+JQ0KICBwaXZvdF9sb25nZXIoY29scz0hZ2VudXMsDQogICAgICAgICAgICAgICBuYW1lc190byA9IGMoIm1vZGVsIiwidHlwZSIsImRhdGFfdmFsdWUiKSwNCiAgICAgICAgICAgICAgIG5hbWVzX3NlcCA9ICJcXC4iKSU+JQ0KICBtdXRhdGUobmFtZT1wYXN0ZTAoZ2VudXMsIi4iLHR5cGUsIi4iLGRhdGFfdmFsdWUpKSU+JQ0KICBwaXZvdF93aWRlcihpZF9jb2xzPSJtb2RlbCIpJT4lDQogIG11dGF0ZShDb2Njb3N0ZXVzLmluZGl2aWR1YWxfZGF0YS5QRT1wYXN0ZTAoIigiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwcmludGYoIiUuMWYiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3VuZChDb2Njb3N0ZXVzLmluZGl2aWR1YWxfZGF0YS5QRV9sb3dlciwxKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICLigJMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwcmludGYoIiUuMWYiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3VuZChDb2Njb3N0ZXVzLmluZGl2aWR1YWxfZGF0YS5QRV91cHBlciwxKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIpIiksDQogICAgICAgICBDb2Njb3N0ZXVzLnNwZWNpZXNfYXZlcmFnZXMuUEU9cGFzdGUwKCIoIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3ByaW50ZigiJS4xZiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKENvY2Nvc3RldXMuc3BlY2llc19hdmVyYWdlcy5QRV9sb3dlciwxKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICLigJMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcHJpbnRmKCIlLjFmIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoQ29jY29zdGV1cy5zcGVjaWVzX2F2ZXJhZ2VzLlBFX3VwcGVyLDEpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIikiKSwNCiAgICAgICAgIER1bmtsZW9zdGV1cy5pbmRpdmlkdWFsX2RhdGEuUEU9cGFzdGUwKCIoIiwNCiAgICAgICAgICAgc3ByaW50ZigiJS4xZiIscm91bmQoRHVua2xlb3N0ZXVzLmluZGl2aWR1YWxfZGF0YS5QRV9sb3dlciwxKSksDQogICAgICAgICAgICLigJMiLA0KICAgICAgICAgICBzcHJpbnRmKCIlLjFmIixyb3VuZChEdW5rbGVvc3RldXMuaW5kaXZpZHVhbF9kYXRhLlBFX3VwcGVyLDEpKSwNCiAgICAgICAgICAgIikiKSwNCiAgICAgICAgIER1bmtsZW9zdGV1cy5zcGVjaWVzX2F2ZXJhZ2VzLlBFPXBhc3RlMCgiKCIsDQogICAgICAgICAgIHNwcmludGYoIiUuMWYiLHJvdW5kKER1bmtsZW9zdGV1cy5zcGVjaWVzX2F2ZXJhZ2VzLlBFX2xvd2VyLDEpKSwNCiAgICAgICAgICAgIuKAkyIsDQogICAgICAgICAgIHNwcmludGYoIiUuMWYiLHJvdW5kKER1bmtsZW9zdGV1cy5zcGVjaWVzX2F2ZXJhZ2VzLlBFX3VwcGVyLDEpKSwNCiAgICAgICAgICAgIikiKSwNCiAgICAgICAgIENvY2Nvc3RldXMuaW5kaXZpZHVhbF9kYXRhLnJhbmdlPXBhc3RlMCgiKCIsDQogICAgICAgICAgIHNwcmludGYoIiUuMWYiLHJvdW5kKENvY2Nvc3RldXMuaW5kaXZpZHVhbF9kYXRhLmxvd2VyLDEpKSwNCiAgICAgICAgICAgIuKAkyIsDQogICAgICAgICAgIHNwcmludGYoIiUuMWYiLHJvdW5kKENvY2Nvc3RldXMuaW5kaXZpZHVhbF9kYXRhLnVwcGVyLDEpKSwNCiAgICAgICAgICAgIikiKSwNCiAgICAgICAgIENvY2Nvc3RldXMuc3BlY2llc19hdmVyYWdlcy5yYW5nZT1wYXN0ZTAoIigiLA0KICAgICAgICAgICBzcHJpbnRmKCIlLjFmIixyb3VuZChDb2Njb3N0ZXVzLnNwZWNpZXNfYXZlcmFnZXMubG93ZXIsMSkpLA0KICAgICAgICAgICAi4oCTIiwNCiAgICAgICAgICAgc3ByaW50ZigiJS4xZiIscm91bmQoQ29jY29zdGV1cy5zcGVjaWVzX2F2ZXJhZ2VzLnVwcGVyLDEpKSwNCiAgICAgICAgICAgIikiKSwNCiAgICAgICAgIER1bmtsZW9zdGV1cy5pbmRpdmlkdWFsX2RhdGEucmFuZ2U9cGFzdGUwKCIoIiwNCiAgICAgICAgICAgc3ByaW50ZigiJS4xZiIscm91bmQoRHVua2xlb3N0ZXVzLmluZGl2aWR1YWxfZGF0YS5sb3dlciwxKSksDQogICAgICAgICAgICLigJMiLA0KICAgICAgICAgICBzcHJpbnRmKCIlLjFmIixyb3VuZChEdW5rbGVvc3RldXMuaW5kaXZpZHVhbF9kYXRhLnVwcGVyLDEpKSwNCiAgICAgICAgICAgIikiKSwNCiAgICAgICAgIER1bmtsZW9zdGV1cy5zcGVjaWVzX2F2ZXJhZ2VzLnJhbmdlPXBhc3RlMCgiKCIsDQogICAgICAgICAgIHNwcmludGYoIiUuMWYiLHJvdW5kKER1bmtsZW9zdGV1cy5zcGVjaWVzX2F2ZXJhZ2VzLmxvd2VyLDEpKSwNCiAgICAgICAgICAgIuKAkyIsDQogICAgICAgICAgIHNwcmludGYoIiUuMWYiLHJvdW5kKER1bmtsZW9zdGV1cy5zcGVjaWVzX2F2ZXJhZ2VzLnVwcGVyLDEpKSwNCiAgICAgICAgICAgIikiKSklPiUNCiAgc2VsZWN0KG1vZGVsLA0KICAgICAgICAgRHVua2xlb3N0ZXVzLmluZGl2aWR1YWxfZGF0YS5maXR0ZWQsRHVua2xlb3N0ZXVzLmluZGl2aWR1YWxfZGF0YS5QRSwNCiAgICAgICAgIER1bmtsZW9zdGV1cy5pbmRpdmlkdWFsX2RhdGEucmFuZ2UsDQogICAgICAgICBEdW5rbGVvc3RldXMuc3BlY2llc19hdmVyYWdlcy5maXR0ZWQsRHVua2xlb3N0ZXVzLnNwZWNpZXNfYXZlcmFnZXMuUEUsDQogICAgICAgICBEdW5rbGVvc3RldXMuc3BlY2llc19hdmVyYWdlcy5yYW5nZSwNCiAgICAgICAgIENvY2Nvc3RldXMuaW5kaXZpZHVhbF9kYXRhLmZpdHRlZCxDb2Njb3N0ZXVzLmluZGl2aWR1YWxfZGF0YS5QRSwNCiAgICAgICAgIENvY2Nvc3RldXMuaW5kaXZpZHVhbF9kYXRhLnJhbmdlLA0KICAgICAgICAgQ29jY29zdGV1cy5zcGVjaWVzX2F2ZXJhZ2VzLmZpdHRlZCxDb2Njb3N0ZXVzLnNwZWNpZXNfYXZlcmFnZXMuUEUsDQogICAgICAgICBDb2Njb3N0ZXVzLnNwZWNpZXNfYXZlcmFnZXMucmFuZ2UpDQojIEV4cG9ydGluZyB0YWJsZSBmb3IgbGF0ZXIgdXNlDQp3cml0ZS54bHN4KENNTkg1NzY4X2xlbmd0aHMsDQogICAgICAgICAgIkRldm9uaWFuIEZpc2ggVGFsZSBUYWJsZSAzIChDTU5IIDU3NjggTGVuZ3RocykueGxzeCIsDQogICAgICAgICAgZmlsZUVuY29kaW5nPSJVVEYtOCIpDQojIENyZWF0aW5nIGthYmxlIGZvciBkaXNwbGF5DQpDTU5INTc2OF9sZW5ndGhzJT4lDQogIGthYmxlKGRpZ2l0cz0xLA0KICAgICAgICBjYXB0aW9uPSJDb21wYXJpc29uIG9mIGxlbmd0aCBlc3RpbWF0ZXMgaW4gPGk+RHVua2xlb3N0ZXVzIHRlcnJlbGxpPC9pPiAoQ01OSCA1NzY4KSBhbmQgPGk+Q29jY29zdGV1cyBjdXNwaWRhdHVzPC9pPiAodXNpbmcgdGhlIHJlY29uc3RydWN0aW9uIGluIE1pbGVzIGFuZCBXZXN0b2xsIDE5NjgpIHVzaW5nIGEgdmFyaWV0eSBvZiBkaWZmZXJlbnQgbW9kZWxzIiwNCiAgICAgICAgY29sLm5hbWVzPWMoIk1vZGVsIiwNCiAgICAgICAgICAgICAgICAgICAgIkVzdC4iLCIrLy0gJVBFIiwiOTUlIFAuSS4iLA0KICAgICAgICAgICAgICAgICAgICAiRXN0LiIsIisvLSAlUEUiLCI5NSUgUC5JLiIsDQogICAgICAgICAgICAgICAgICAgICJFc3QuIiwiKy8tICVQRSIsIjk1JSBQLkkuIiwNCiAgICAgICAgICAgICAgICAgICAgIkVzdC4iLCIrLy0gJVBFIiwiOTUlIFAuSS4iKSklPiUNCiAgY29sdW1uX3NwZWMoYygyLDUsOCwxMSksIGJvbGQgPSBUKSU+JQ0KICBhZGRfaGVhZGVyX2Fib3ZlKGMoIiAiPTEsIkluZGl2aWR1YWwgRGF0YSI9MywNCiAgICAgICAgICAgICAgICAgICAgICJTcGVjaWVzIEF2ZXJhZ2VzIj0zLA0KICAgICAgICAgICAgICAgICAgICAgIkluZGl2aWR1YWwgRGF0YSI9MywNCiAgICAgICAgICAgICAgICAgICAgICJTcGVjaWVzIEF2ZXJhZ2VzIj0zKSklPiUNCiAgYWRkX2hlYWRlcl9hYm92ZShjKCIgIj0xLCJEdW5rbGVvc3RldXMgdGVycmVsbGkgKENNTkggNTc2OCkiPTYsDQogICAgICAgICAgICAgICAgICAgICAiQ29jY29zdGV1cyBjdXNwaWRhdHVzIChNICYgVyAxOTY4KSI9NikpJT4lDQogIGthYmxlX3N0eWxpbmcoKQ0KYGBgDQoNClRoZSByZWFzb24gd2h5IHNvbWUgb2YgdGhlIGVzdGltYXRlcyB1c2luZyBpbmRpdmlkdWFsIGRhdGEgc2VlbSB0byBiZSBoaWdoICgzLjctMy44IG0pIGFwcGVhcnMgdG8gYmUgZGlyZWN0bHkgZHVlIHRvIGluY2x1ZGluZyBhIHZlcnkgbGFyZ2Ugc2FtcGxlIG9mICpLYWppa2lhIGFsYmlkYSogKDU3IHNwZWNpbWVucywgUm9iaW5zIDE5NzQpIGFuZCAqVGV0cmFwdHVydXMqIHNwcC4gKH4xMSBzcGVjaW1lbnMgb2YgKlQuIHBmbHVlZ2VyaSogYW5kIH4zNiBpbmRpdmlkdWFscyBvZiAqVC4gYmVsb25lKjsgUm9iaW5zIGFuZCBkZSBTeWx2YSAxOTYzKS4gVGhlc2UgZmlzaGVzIGFsbCBoYXZlIHJhdGhlciBlbG9uZ2F0ZSBib2RpZXMsIGFuZCB0aGUgc2hlZXIgbnVtYmVyIG9mIGluZGl2aWR1YWxzIGJlaW5nIGNvdW50ZWQgcmVzdWx0cyBpbiB0aGUgZXN0aW1hdGVzIGJlaW5nIGJpYXNlZCB1cHdhcmRzIHdoZW4gdGhleSBhcmUgY29uc2lkZXJlZC4gVGhpcyBjYW4gYmUgc2VlbiBpbiB0aGUgZmFjdCB0aGF0IGVzdGltYXRlcyBmb3IgKkNvY2Nvc3RldXMqIGFyZSB2ZXJ5IGNvbnNpc3RlbnQgYWNyb3NzIGluZGl2aWR1YWwgc3BlY2ltZW4gYW5kIHNwZWNpZXMtYXZlcmFnZSBtb2RlbHMsIGFuZCBpbiBlYXJsaWVyIGl0ZXJhdGlvbnMgb2YgdGhlIGRhdGFiYXNlIHByaW9yIHRvIHRoZSBhZGRpdGlvbiBvZiBkYXRhIGZyb20gUm9iaW5zICgxOTc0KSB0aGUgcmVzdWx0cyBiZXR3ZWVuIGluZGl2aWR1YWwgZGF0YSBhbmQgc3BlY2llcy1hdmVyYWdlIG1vZGVscyB3ZXJlIG5lYXJseSBpZGVudGljYWwgZm9yICpEdW5rbGVvc3RldXMgdGVycmVsbGkqLg0KDQojIEVzdGltYXRpbmcgbGVuZ3RoIG9mIHRoZSBsYXJnZXN0IHNwZWNpbWVucyBvZiAqRHVua2xlb3N0ZXVzIHRlcnJlbGxpKiAoQ01OSCA1OTM2KQ0KDQojIyBFeGFtaW5pbmcgY29ycmVsYXRpb24gYmV0d2VlbiBiaXRpbmcgcG9ydGlvbiBvZiB0aGUgaW5mZXJvZ25hdGhhbCBhbmQgb3JiaXQtb3BlcmN1bGFyIGxlbmd0aA0KDQpgYGB7cixjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30NCiMgUmVhZGluZyBpbiBEdW5rbGVvc3RldXMgbWVhc3VyZW1lbnQgZGF0YQ0KZHVua2xlb3N0ZXVzX2phd3M8LXJlYWQuY3N2KCJEZXZvbmlhbiBGaXNoIFRhbGUgU3VwcGxlbWVudGFyeSBGaWxlIDIgKER1bmtsZW9zdGV1cyBNZWFzdXJlbWVudHMpLmNzdiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyPVQpJT4lDQogIHJlbmFtZSgiT09MIj0iT09MIiklPiUNCiAgbXV0YXRlKGNsYWRlPSJQbGFjb2Rlcm1pIixoaWdoZXJfZ3JvdXA9IkFydGhyb2RpcmEiLG9yZGVyPSJBcnRocm9kaXJhIiwNCiAgICAgICAgIGZhbWlseT0iRHVua2xlb3N0ZWlkYWUiLGhhYml0YXQ9InBlbGFnaWMiLHNoYXBlPSJmdXNpZm9ybSIpJT4lDQogIGF1Z21lbnQoZml0LnNoYXBlX2NsYWRlMyxuZXdkYXRhPS4saW50ZXJ2YWw9InByZWRpY3QiKSU+JQ0KICBtdXRhdGUoYWNyb3NzKC5maXR0ZWQ6LnVwcGVyLH5leHAoLikqcmVncmVzc2lvbi5zdGF0cyhmaXQuc2hhcGVfY2xhZGUzKSRDRikpJT4lDQogIHJlbmFtZShmaXQ9LmZpdHRlZCxsd3I9Lmxvd2VyLHVwcj0udXBwZXIpJT4lDQogIHNlbGVjdChzcGVjaW1lbixKTTEsSk0yLEpNMyxKTTQsSk01LGluZmVyb2duYXRoYWxfbGVuZ3RoLE9PTCxoZWFkX2xlbmd0aCwNCiAgICAgICAgIGZpdCxsd3IsdXByKQ0KDQojIFByaW50aW5nIER1bmtsZW9zdGV1cyBtZWFzdXJlbWVudCBkYXRhDQpkdW5rbGVvc3RldXNfamF3cyU+JQ0KICByZW1vdmVfcm93bmFtZXMoKSU+JWNvbHVtbl90b19yb3duYW1lcygic3BlY2ltZW4iKSU+JQ0KICBhcnJhbmdlKEpNNSklPiUNCiAgc2VsZWN0KC1jKGZpdCxsd3IsdXByKSklPiUNCiAga2FibGUoZGlnaXRzPTEsYWxpZ249ImMiLA0KICAgICAgICBjYXB0aW9uPSJTa2VsZXRhbCBtZWFzdXJlbWVudHMgb2YgdGhlIHNwZWNpbWVucyBvZiA8aT5EdW5rbGVvc3RldXMgdGVycmVsbGk8L2k+IGNvbnNpZGVyZWQgaGVyZS4gQWxsIG1lYXN1cmVtZW50cyBpbiBjbS4iLA0KICAgICAgICBjb2wubmFtZXM9YygiSk0xIiwiSk0yIiwiSk0zIiwiSk00IiwiSk01IiwiSW5mZXJvZ25hdGhhbCBMZW5ndGgiLCJPT0wiLCJIZWFkIExlbmd0aCIpKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQpOb3RlIHRoYXQgQ01OSCA1NzY4IHdhcyBpbmNsdWRlZCB0d2ljZSBiZWNhdXNlIHRoZXJlIGlzIHNvbWUgdGFwaG9ub21pYyBkaXN0b3J0aW9uIGJldHdlZW4gdGhlIGxlZnQgYW5kIHJpZ2h0IGluZmVyb2duYXRoYWwsIHdoaWNoIGNvdWxkIHBvdGVudGlhbGx5IGJpYXMgZXN0aW1hdGVzIG9mIENNTkggNTkzNiBpZiBub3QgYWNjb3VudGVkIGZvciBhcyBDTU5IIDU3NjggaXMgdGhlIGxhcmdlc3QgY29tcGxldGUgaW5kaXZpZHVhbCBpbiB0aGlzIHN0dWR5IGJ5IGEgbGFyZ2UgbWFyZ2luLg0KDQojIyMgSk0xDQoNCmBgYHtyLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0Kc3VtbWFyeShsbShPT0x+Sk0xLGR1bmtsZW9zdGV1c19qYXdzKSkNCkpNMV9hbGxvbWV0cnk8LWxtKGxvZyhKTTEpfmxvZyhPT0wpLGR1bmtsZW9zdGV1c19qYXdzKQ0KYGBgDQoNCihyZWY6Sk0xKSBQbG90IG9mIEpNMSB2ZXJzdXMgb3JiaXQtb3BlcmN1bGFyIGxlbmd0aCBpbiBzcGVjaW1lbnMgb2YgKkR1bmtsZW9zdGV1cyB0ZXJyZWxsaSogZm9yIHdoaWNoIGJvdGggbWVhc3VyZW1lbnRzIGFyZSBrbm93bi4gTGVuZ3RoIGJldHdlZW4gbWVkaWFsIGFuZCBwb3N0ZXJpb3IgY3VzcCB0byBKTTEgaW4gRmVycsOzbiBldCBhbC4gKDIwMTcpLg0KDQpgYGB7cixmaWcuY2FwPSIocmVmOkpNMSkiLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KZHVua2xlb3N0ZXVzX2phd3MlPiUNCiAgZHJvcF9uYShPT0wsSk0xKSU+JQ0KICBnZ3Bsb3QoYWVzKHk9T09MLHg9Sk0xKSkrDQogIGdlb21fc21vb3RoKG1ldGhvZD0ibG0iLGZvcm11bGE9eX54KSsNCiAgZ2VvbV9wb2ludChmaWxsPSJibGFjayIsY29sb3I9IndoaXRlIixzaGFwZT0yMSxzaXplPTMpKw0KICBsYWJzKHg9Ikxlbmd0aCBCZXR3ZWVuIE1lZGlhbCBhbmQgUG9zdGVyaW9yIEN1c3AgKGNtKSIsDQogICAgICAgeT0iT3JiaXQtT3BlcmN1bGFyIExlbmd0aCAoY20pIikrDQogIHRoZW1lX2NsYXNzaWMoKSsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1zcGVjaW1lbiwNCiAgICAgICAgICAgICAgICB4PWlmZWxzZShzcGVjaW1lbiAlaW4lIGMoIkNNTkggNTc2OCAobGVmdCkiLCJDTU5IIDcwNTQgKHJpZ2h0KSIpLEpNMS0wLjI1LA0KICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShzcGVjaW1lbiAlaW4lIGMoIkNNTkggNjA5MCAocmlnaHQpIiksSk0xKzAuMjUsSk0xKSksDQogICAgICAgICAgICAgICAgeT1pZmVsc2Uoc3BlY2ltZW4gJWluJSBjKCJDTU5IIDU3NjggKGxlZnQpIiwiQ01OSCA2MDkwIChyaWdodCkiKSxPT0wrMS41LA0KICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShzcGVjaW1lbiAlaW4lIGMoIkNNTkggNzA1NCAocmlnaHQpIiksT09MLTEuNSxPT0wpKSwNCiAgICAgICAgICAgICAgICBoanVzdD1pZmVsc2Uoc3BlY2ltZW4gJWluJSBjKCJDTU5IIDU3NjggKGxlZnQpIiwiQ01OSCA3MDU0IChyaWdodCkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNNTkggNzA1NCAobGVmdCkiKSwxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2Uoc3BlY2ltZW4gJWluJSBjKCJDTU5IIDYwOTAgKHJpZ2h0KSIsIkNNTkggODEzMSIpLDAsMC41KSkpLA0KICAgICAgICAgICAgbnVkZ2VfeT1pZmVsc2UocmVzaWR1YWxzKGxtKE9PTH5KTTEsZHVua2xlb3N0ZXVzX2phd3MlPiVkcm9wX25hKEpNMSxPT0wpKSk8MCwtMSwxKSkNCmBgYA0KDQojIyMgSk0yDQoNCmBgYHtyLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0Kc3VtbWFyeShsbShPT0x+Sk0yLGR1bmtsZW9zdGV1c19qYXdzKSkNCkpNMl9hbGxvbWV0cnk8LWxtKGxvZyhKTTIpfmxvZyhPT0wpLGR1bmtsZW9zdGV1c19qYXdzKQ0KYGBgDQoNCihyZWY6Sk0yKSBQbG90IG9mIEpNMiB2ZXJzdXMgb3JiaXQtb3BlcmN1bGFyIGxlbmd0aCBpbiBzcGVjaW1lbnMgb2YgKkR1bmtsZW9zdGV1cyB0ZXJyZWxsaSogZm9yIHdoaWNoIGJvdGggbWVhc3VyZW1lbnRzIGFyZSBrbm93bi4gTGVuZ3RoIGJldHdlZW4gc3ltcGh5c2VhbCBjdXNwIGFuZCBhY2Nlc3NvcnkgY3VzcCBpcyBKTTIgaW4gRmVycsOzbiBldCBhbC4gKDIwMTcpLg0KDQpgYGB7cixmaWcuY2FwPSIocmVmOkpNMikiLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KZHVua2xlb3N0ZXVzX2phd3MlPiUNCiAgZHJvcF9uYShPT0wsSk0yKSU+JQ0KICBnZ3Bsb3QoYWVzKHk9T09MLHg9Sk0yKSkrDQogIGdlb21fc21vb3RoKG1ldGhvZD0ibG0iLGZvcm11bGE9eX54KSsNCiAgZ2VvbV9wb2ludChmaWxsPSJibGFjayIsY29sb3I9IndoaXRlIixzaGFwZT0yMSxzaXplPTMpKw0KICBsYWJzKHg9Ikxlbmd0aCBCZXR3ZWVuIFN5bXBoeXNlYWwgYW5kIEFjY2Vzc29yeSBDdXNwIChjbSkiLA0KICAgICAgIHk9Ik9yYml0LU9wZXJjdWxhciBMZW5ndGggKGNtKSIpKw0KICB0aGVtZV9jbGFzc2ljKCkrDQogIGdlb21fdGV4dChhZXMobGFiZWw9c3BlY2ltZW4sDQogICAgICAgICAgICAgICAgeT1pZmVsc2Uoc3BlY2ltZW4gJWluJSBjKCJDTU5IIDgxMzEiLCJDTU5IIDcwNTQgKGxlZnQpIiwiQ01OSCA2MTk0IChyaWdodCkiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBPT0wrMixPT0wpLA0KICAgICAgICAgICAgICAgIHg9aWZlbHNlKHNwZWNpbWVuICVpbiUgYygiQ01OSCA4MTMxIiwiQ01OSCA3MDU0IChyaWdodCkiKSxKTTIrMC4xLEpNMiksDQogICAgICAgICAgICAgICAgaGp1c3Q9aWZlbHNlKHNwZWNpbWVuICVpbiUgYygiQ01OSCA4MTMxIiwiQ01OSCA3MDU0IChyaWdodCkiLCJDTU5IIDYxOTQgKGxlZnQpIiksMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHNwZWNpbWVuICVpbiUgYygiQ01OSCA3MDU0IChsZWZ0KSIsIkNNTkggNjE5NCAocmlnaHQpIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ01OSCA2MDkwIChyaWdodCkiKSwxLDAuNSkpKSwNCiAgICAgICAgICAgIG51ZGdlX3k9aWZlbHNlKHJlc2lkdWFscyhsbShPT0x+Sk0yLGR1bmtsZW9zdGV1c19qYXdzJT4lZHJvcF9uYShKTTIsT09MKSkpPDAsLTEsMSkpDQpgYGANCg0KIyMjIEpNMw0KDQpgYGB7cixjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30NCnN1bW1hcnkobG0oT09MfkpNMyxkdW5rbGVvc3RldXNfamF3cykpDQpmaXQuSk0zPC1sbShPT0x+Sk0zLGR1bmtsZW9zdGV1c19qYXdzKQ0KSk0zX2FsbG9tZXRyeTwtbG0obG9nKE9PTCl+bG9nKEpNMyksZHVua2xlb3N0ZXVzX2phd3MpDQpgYGANCg0KKHJlZjpKTTMpIFBsb3Qgb2YgSk0zIHZlcnN1cyBvcmJpdC1vcGVyY3VsYXIgbGVuZ3RoIGluIHNwZWNpbWVucyBvZiAqRHVua2xlb3N0ZXVzIHRlcnJlbGxpKiBmb3Igd2hpY2ggYm90aCBtZWFzdXJlbWVudHMgYXJlIGtub3duLiBIZWlnaHQgb2YgdGhlIGluZmVyb2duYXRoYWwgYXQgcG9zdGVyaW9yIGN1c3AgaXMgSk0zIGluIEZlcnLDs24gZXQgYWwuICgyMDE3KS4NCg0KYGBge3IsZmlnLmNhcD0iKHJlZjpKTTMpIixjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30NCmR1bmtsZW9zdGV1c19qYXdzJT4lDQogIGZpbHRlcighaXMubmEoT09MKSklPiUNCiAgZ2dwbG90KGFlcyh5PU9PTCx4PUpNMykpKw0KICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIixmb3JtdWxhPXl+eCkrDQogIGdlb21fcG9pbnQoZmlsbD0iYmxhY2siLGNvbG9yPSJ3aGl0ZSIsc2hhcGU9MjEsc2l6ZT0zKSsNCiAgbGFicyh4PSJIZWlnaHQgb2YgSW5mZXJvZ25hdGhhbCBhdCBQb3N0ZXJpb3IgQ3VzcCAoY20pIiwNCiAgICAgICB5PSJPcmJpdC1PcGVyY3VsYXIgTGVuZ3RoIChjbSkiKSsNCiAgdGhlbWVfY2xhc3NpYygpKw0KICBnZW9tX3RleHQoYWVzKHg9aWZlbHNlKHNwZWNpbWVuICVpbiUgYygiQ01OSCA4MTMxIiksSk0zKzAuNSxKTTMpLA0KICAgICAgICAgICAgICAgIHk9aWZlbHNlKHNwZWNpbWVuICVpbiUgYygiQ01OSCA4MTMxIiwiQ01OSCA2MTk0IChyaWdodCkiKSxPT0wrMSxPT0wpLA0KICAgICAgICAgICAgICAgIGxhYmVsPXNwZWNpbWVuLA0KICAgICAgICAgICAgICAgIGhqdXN0PWlmZWxzZShzcGVjaW1lbiAlaW4lIGMoIkNNTkggNTc2OCAobGVmdCkiLCJDTU5IIDYxOTQgKHJpZ2h0KSIpLDAuNSwNCiAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHJlc2lkdWFscyhsbShPT0x+Sk0zLGR1bmtsZW9zdGV1c19qYXdzJT4lZHJvcF9uYShKTTMsT09MKSkpPDAsMCwxKSkpLA0KICAgICAgICAgICAgbnVkZ2VfeT1pZmVsc2UocmVzaWR1YWxzKGxtKE9PTH5KTTMsZHVua2xlb3N0ZXVzX2phd3MlPiVkcm9wX25hKEpNMyxPT0wpKSk8MCwtMSwxKSkNCmBgYA0KDQojIyMgSk00DQoNCmBgYHtyLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0Kc3VtbWFyeShsbShPT0x+Sk00LGR1bmtsZW9zdGV1c19qYXdzKSkNCkpNNF9hbGxvbWV0cnk8LWxtKGxvZyhKTTQpfmxvZyhPT0wpLGR1bmtsZW9zdGV1c19qYXdzKQ0KYGBgDQoNCihyZWY6Sk00KSBQbG90IG9mIEpNNCB2ZXJzdXMgb3JiaXQtb3BlcmN1bGFyIGxlbmd0aCBpbiBzcGVjaW1lbnMgb2YgKkR1bmtsZW9zdGV1cyB0ZXJyZWxsaSogZm9yIHdoaWNoIGJvdGggbWVhc3VyZW1lbnRzIGFyZSBrbm93bi4gQml0aW5nIHBvcnRpb24gb2YgdGhlIGluZmVyb2duYXRoYWwgaXMgSk00IGluIEZlcnLDs24gZXQgYWwuICgyMDE3KS4NCg0KYGBge3IsZmlnLmNhcD0iKHJlZjpKTTQpIixjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30NCmR1bmtsZW9zdGV1c19qYXdzJT4lDQogIGRyb3BfbmEoT09MLEpNNCklPiUNCiAgZ2dwbG90KGFlcyh5PU9PTCx4PUpNNCkpKw0KICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIixmb3JtdWxhPXl+eCkrDQogIGdlb21fcG9pbnQoZmlsbD0iYmxhY2siLGNvbG9yPSJ3aGl0ZSIsc2hhcGU9MjEsc2l6ZT0zKSsNCiAgbGFicyh4PSJIZWlnaHQgb2YgSW5mZXJvZ25hdGhhbCBhdCBBY2Nlc3NvcnkgQ3VzcHMgKGNtKSIsDQogICAgICAgeT0iT3JiaXQtT3BlcmN1bGFyIExlbmd0aCAoY20pIikrDQogIHRoZW1lX2NsYXNzaWMoKSsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1zcGVjaW1lbiwNCiAgICAgICAgICAgICAgICB4PWlmZWxzZShzcGVjaW1lbiAlaW4lIGMoIkNNTkggNjA5MCAobGVmdCkiKSxKTTQtMC4yNSxKTTQpLA0KICAgICAgICAgICAgICAgIHk9aWZlbHNlKHNwZWNpbWVuICVpbiUgYygiQ01OSCA2MDkwIChsZWZ0KSIpLE9PTC0xLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShzcGVjaW1lbiAlaW4lIGMoIkNNTkggNzA1NCAobGVmdCkiKSxPT0wrMi41LE9PTCkpLA0KICAgICAgICAgICAgICAgIGhqdXN0PWlmZWxzZShzcGVjaW1lbiAlaW4lIGMoIkNNTkggNjA5MCAobGVmdCkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNNTkggNTc2OCAobGVmdCkiKSwxLDAuNSkpLA0KICAgICAgICAgICAgbnVkZ2VfeT1pZmVsc2UocmVzaWR1YWxzKGxtKE9PTH5KTTQsZHVua2xlb3N0ZXVzX2phd3MlPiVkcm9wX25hKEpNNCxPT0wpKSk8MCwtMSwxKSkNCmBgYA0KDQojIyMgSk01DQoNCmBgYHtyLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KZml0LkpNNTwtbG0oT09MfkpNNSxkdW5rbGVvc3RldXNfamF3cykNCnN1bW1hcnkoZml0LkpNNSkNCkpNNV9hbGxvbWV0cnk8LWxtKGxvZyhKTTUpfmxvZyhPT0wpLGR1bmtsZW9zdGV1c19qYXdzKQ0KYGBgDQoNCihyZWY6Sk01KSBQbG90IG9mIEpNNSB2ZXJzdXMgb3JiaXQtb3BlcmN1bGFyIGxlbmd0aCBpbiBzcGVjaW1lbnMgb2YgKkR1bmtsZW9zdGV1cyB0ZXJyZWxsaSogZm9yIHdoaWNoIGJvdGggbWVhc3VyZW1lbnRzIGFyZSBrbm93bi4gTGVuZ3RoIG9mIGJpdGluZyBwb3J0aW9uIG9mIHRoZSBpbmZlcm9nbmF0aGFsIGlzIEpNNSBpbiBGZXJyw7NuIGV0IGFsLiAoMjAxNykuDQoNCmBgYHtyLGZpZy5jYXA9IihyZWY6Sk01KSIsY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZSd9DQpkdW5rbGVvc3RldXNfamF3cyU+JQ0KICBmaWx0ZXIoIWlzLm5hKE9PTCkpJT4lDQogIGdncGxvdChhZXMoeT1PT0wseD1KTTUpKSsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSJsbSIsZm9ybXVsYT15fngpKw0KICBnZW9tX3BvaW50KGZpbGw9ImJsYWNrIixjb2xvcj0id2hpdGUiLHNoYXBlPTIxLHNpemU9MykrDQogIGxhYnMoeD0iTGVuZ3RoIG9mIEJpdGluZyBQb3J0aW9uIG9mIEluZmVyb2duYXRoYWwgKGNtKSIsDQogICAgICAgeT0iT3JiaXQtT3BlcmN1bGFyIExlbmd0aCAoY20pIikrDQogIHRoZW1lX2NsYXNzaWMoKSsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1zcGVjaW1lbiwNCiAgICAgICAgICAgICAgICB4PWlmZWxzZShzcGVjaW1lbiAlaW4lIGMoIkNNTkggODEzMSIsIkNNTkggNjE5NCAocmlnaHQpIiksSk01KzAuNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2Uoc3BlY2ltZW4gJWluJSAoIkNNTkggNjA5MCAobGVmdCkiKSxKTTUtMC41LEpNNSkpLA0KICAgICAgICAgICAgICAgIHk9aWZlbHNlKHNwZWNpbWVuICVpbiUgYygiQ01OSCA4MTMxIiksT09MKzEsDQogICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHNwZWNpbWVuICVpbiUgYygiQ01OSCA2MTk0IChyaWdodCkiKSxPT0wrMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2Uoc3BlY2ltZW4gJWluJSBjKCJDTU5IIDYwOTAgKGxlZnQpIiksT09MLTEsT09MKSkpLA0KICAgICAgICAgICAgICAgIGhqdXN0PWlmZWxzZShzcGVjaW1lbiAlaW4lIGMoIkNNTkggODEzMSIsIkNNTkggNzA1NCAobGVmdCkiLCJDTU5IIDYxOTQgKGxlZnQpIiksMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHNwZWNpbWVuICVpbiUgYygiQ01OSCA2MDkwIChsZWZ0KSIsIkNNTkggNzA1NCAocmlnaHQpIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ01OSCA1NzY4IChsZWZ0KSIpLDEsMC41KSkNCiAgICAgICAgICAgICAgICApLG51ZGdlX3k9aWZlbHNlKHJlc2lkdWFscyhsbShPT0x+Sk01LGR1bmtsZW9zdGV1c19qYXdzJT4lZHJvcF9uYShKTTUsT09MKSkpIDwgMCwgLTEsIDEpKQ0KYGBgDQoNCiMjIyBJbmZlcm9nbmF0aGFsIGxlbmd0aA0KDQpgYGB7cixjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30NCmluZmVyb2duYXRoYWxfbGVuZ3RoPC1sbShPT0x+aW5mZXJvZ25hdGhhbF9sZW5ndGgsZHVua2xlb3N0ZXVzX2phd3MpDQpzdW1tYXJ5KGluZmVyb2duYXRoYWxfbGVuZ3RoKQ0KaW5mZXJvZ25hdGhhbF9sZW5ndGhfYWxsb21ldHJ5PC1sbShsb2coaW5mZXJvZ25hdGhhbF9sZW5ndGgpfmxvZyhPT0wpLGR1bmtsZW9zdGV1c19qYXdzKQ0KYGBgDQoNCihyZWY6aW5mZXJvZ25hdGhhbGxlbmd0aCkgUGxvdCBvZiB0b3RhbCBpbmZlcm9nbmF0aGFsIGxlbmd0aCB2ZXJzdXMgb3JiaXQtb3BlcmN1bGFyIGxlbmd0aCBpbiBzcGVjaW1lbnMgb2YgKkR1bmtsZW9zdGV1cyB0ZXJyZWxsaSogZm9yIHdoaWNoIGJvdGggbWVhc3VyZW1lbnRzIGFyZSBrbm93bi4NCg0KYGBge3IsZmlnLmNhcD0iKHJlZjppbmZlcm9nbmF0aGFsbGVuZ3RoKSIsY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZSd9DQpkdW5rbGVvc3RldXNfamF3cyU+JQ0KICBmaWx0ZXIoIWlzLm5hKE9PTCkpJT4lDQogIGdncGxvdChhZXMoeT1PT0wseD1pbmZlcm9nbmF0aGFsX2xlbmd0aCkpKw0KICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIixmb3JtdWxhPXl+eCkrDQogIGdlb21fcG9pbnQoZmlsbD0iYmxhY2siLGNvbG9yPSJ3aGl0ZSIsc2hhcGU9MjEsc2l6ZT0zKSsNCiAgbGFicyh4PSJJbmZlcm9nbmF0aGFsIExlbmd0aCAoY20pIiwNCiAgICAgICB5PSJPcmJpdC1PcGVyY3VsYXIgTGVuZ3RoIChjbSkiKSsNCiAgdGhlbWVfY2xhc3NpYygpKw0KICBnZW9tX3RleHQoYWVzKHg9aWZlbHNlKHNwZWNpbWVuICVpbiUgYygiQ01OSCA1NzY4IChsZWZ0KSIsIkNNTkggNjA5MCAobGVmdCkiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBpbmZlcm9nbmF0aGFsX2xlbmd0aC0wLjUsDQogICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHNwZWNpbWVuICVpbiUgYygiQ01OSCA4MTMxIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgaW5mZXJvZ25hdGhhbF9sZW5ndGgrMC41LA0KICAgICAgICAgICAgICAgICAgICAgICAgIGluZmVyb2duYXRoYWxfbGVuZ3RoKSksDQogICAgICAgICAgICAgICAgeT1pZmVsc2Uoc3BlY2ltZW4gJWluJSBjKCJDTU5IIDYwOTAgKGxlZnQpIiwiQ01OSCA1NzY4IChsZWZ0KSIpLE9PTC0xLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShzcGVjaW1lbiAlaW4lIGMoIkNNTkggODEzMSIpLE9PTCsxLE9PTCkpLA0KICAgICAgICAgICAgICAgIGxhYmVsPXNwZWNpbWVuLA0KICAgICAgICAgICAgICAgIGhqdXN0PWlmZWxzZShzcGVjaW1lbiAlaW4lIGMoIkNNTkggODEzMSIsIkNNTkggNzA1NCAobGVmdCkiLCJDTU5IIDYxOTQgKGxlZnQpIiksMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHNwZWNpbWVuICVpbiUgYygiQ01OSCA2MDkwIChsZWZ0KSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNNTkggNTc2OCAobGVmdCkiKSwxLDAuNSkpKSwNCiAgICAgICAgICAgIG51ZGdlX3k9aWZlbHNlKHJlc2lkdWFscyhsbShPT0x+aW5mZXJvZ25hdGhhbF9sZW5ndGgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHVua2xlb3N0ZXVzX2phd3MlPiVkcm9wX25hKGluZmVyb2duYXRoYWxfbGVuZ3RoLE9PTCkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApKTwwLC0xLDEpKQ0KYGBgDQoNCkJhc2VkIG9uIHRoZXNlIHJlc3VsdHMsIHRoZSBsZW5ndGggb2YgdGhlIGJpdGluZyBwb3J0aW9uIG9mIHRoZSBpbmZlcm9nbmF0aGFsIGFuZCB0aGUgaGVpZ2h0IG9mIHRoZSBwb3N0ZXJpb3IgY3VzcCBvZiB0aGUgaW5mZXJvZ25hdGhhbCBhcmUgdGhlIGJlc3QgbWVhc3VyZW1lbnRzIHRvIGVzdGltYXRlIHRvdGFsIGxlbmd0aCB1c2luZyB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiBvcmJpdC1vcGVyY3VsYXIgbGVuZ3RoIGFuZCBpbmZlcm9nbmF0aGFsIGRpbWVuc2lvbnMuIFRoZXNlIGFyZSBhbHNvIHRoZSBkaW1lbnNpb25zIHRoYXQgYXJlIG1vc3Qgcm9idXN0IHRvIHByZXNlcnZhdGlvbmFsIGJpYXNlcyBhbmQgdGFwaG9ub21pYyBkaXN0b3J0aW9uICh0aGUgdG90YWwgbGVuZ3RoIG9mIHRoZSBpbmZlcm9nbmF0aGFsIGlzIG9mdGVuIG5vdCBwcmVzZXJ2ZWQsIGFzIGluIENNTkggNTkzNiksIGluZmVyb2duYXRoYWwgd2VhciAoSk00IGNhbiBiZSB2YXJpYWJsZSBkdWUgdG8gd2VhciksIGFuZCBjYW4gYmUgZWFzaWx5IGlkZW50aWZpZWQgYWNyb3NzIHRheGEgKEpNMSBhbmQgSk0yIGNhbiBiZSBoYXJkIHRvIGlkZW50aWZ5IGluIHdvcm4gamF3cyBhbmQgYXJlIGRlcGVuZGVudCBvbiBjaGFyYWN0ZXJzIHVuaXF1ZSB0byAqRHVua2xlb3N0ZXVzKiwgbWFraW5nIGNvbXBhcmFibGUgcmVncmVzc2lvbiBlcXVhdGlvbnMgZGlmZmljdWx0IHRvIGNvbnN0cnVjdCBmb3Igb3RoZXIgYXJ0aHJvZGlyZXMpLg0KDQojIyBBbGxvbWV0cnkgb2YgaW5mZXJvZ25hdGhhbCBkaW1lbnNpb25zIGluICpEdW5rbGVvc3RldXMgdGVycmVsbGkqDQoNCmBgYHtyLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KZGF0YS5mcmFtZSgiSk0xIj1jKHN1bW1hcnkoSk0xX2FsbG9tZXRyeSkkci5zcXVhcmVkLA0KICAgICAgICAgICAgICAgICAgIHN1bW1hcnkoSk0xX2FsbG9tZXRyeSkkY29lZmZpY2llbnRzWzEsXSwNCiAgICAgICAgICAgICAgICAgICBzdW1tYXJ5KEpNMV9hbGxvbWV0cnkpJGNvZWZmaWNpZW50c1syLF0pLA0KICAgICAgICAgICAiSk0yIj1jKHN1bW1hcnkoSk0yX2FsbG9tZXRyeSkkci5zcXVhcmVkLA0KICAgICAgICAgICAgICAgICAgIHN1bW1hcnkoSk0yX2FsbG9tZXRyeSkkY29lZmZpY2llbnRzWzEsXSwNCiAgICAgICAgICAgICAgICAgICBzdW1tYXJ5KEpNMl9hbGxvbWV0cnkpJGNvZWZmaWNpZW50c1syLF0pLA0KICAgICAgICAgICAiSk0zIj1jKHN1bW1hcnkoSk0zX2FsbG9tZXRyeSkkci5zcXVhcmVkLA0KICAgICAgICAgICAgICAgICAgIHN1bW1hcnkoSk0zX2FsbG9tZXRyeSkkY29lZmZpY2llbnRzWzEsXSwNCiAgICAgICAgICAgICAgICAgICBzdW1tYXJ5KEpNM19hbGxvbWV0cnkpJGNvZWZmaWNpZW50c1syLF0pLA0KICAgICAgICAgICAiSk00Ij1jKHN1bW1hcnkoSk00X2FsbG9tZXRyeSkkci5zcXVhcmVkLA0KICAgICAgICAgICAgICAgICAgIHN1bW1hcnkoSk00X2FsbG9tZXRyeSkkY29lZmZpY2llbnRzWzEsXSwNCiAgICAgICAgICAgICAgICAgICBzdW1tYXJ5KEpNNF9hbGxvbWV0cnkpJGNvZWZmaWNpZW50c1syLF0pLA0KICAgICAgICAgICAiSk01Ij1jKHN1bW1hcnkoSk01X2FsbG9tZXRyeSkkci5zcXVhcmVkLA0KICAgICAgICAgICAgICAgICAgIHN1bW1hcnkoSk01X2FsbG9tZXRyeSkkY29lZmZpY2llbnRzWzEsXSwNCiAgICAgICAgICAgICAgICAgICBzdW1tYXJ5KEpNNV9hbGxvbWV0cnkpJGNvZWZmaWNpZW50c1syLF0pLA0KICAgICAgICAgICAiSW5mZXJvZ25hdGhhbCBMZW5ndGgiPQ0KICAgICAgICAgICAgIGMoc3VtbWFyeShpbmZlcm9nbmF0aGFsX2xlbmd0aF9hbGxvbWV0cnkpJHIuc3F1YXJlZCwNCiAgICAgICAgICAgICAgIHN1bW1hcnkoaW5mZXJvZ25hdGhhbF9sZW5ndGhfYWxsb21ldHJ5KSRjb2VmZmljaWVudHNbMSxdLA0KICAgICAgICAgICAgICAgc3VtbWFyeShpbmZlcm9nbmF0aGFsX2xlbmd0aF9hbGxvbWV0cnkpJGNvZWZmaWNpZW50c1syLF0pKSU+JQ0KICB0KCklPiUNCiAgZGF0YS5mcmFtZShyb3cubmFtZXM9YygiSk0xIiwiSk0yIiwiSk0zIiwiSk00IiwiSk01IiwiSW5mZXJvZ25hdGhhbCBMZW5ndGgiKSklPiUNCiAgc2VsZWN0KFgxLFg2LFg3LFg4LFg5LFgyLFgzLFg0LFg1KSU+JSAjIE1vdmluZyBzbG9wZSB0byBiZSBiZWZvcmUgaW50ZXJjZXB0DQogIGthYmxlKGRpZ2l0cz0zLGFsaWduPSJjIiwNCiAgICAgICAgY29sLm5hbWVzPWMoInIyIiwiRXN0aW1hdGUiLCJTdGQuIEVycm9yIiwiVCBWYWx1ZSIsIlByKD58dHwpIiwiRXN0aW1hdGUiLCJTdGQuIEVycm9yIiwiVCBWYWx1ZSIsIlByKD58dHwpIiksDQogICAgICAgIGNhcHRpb249IkV4YW1pbmF0aW9uIG9mIGFsbG9tZXRyeSBvZiBpbmZlcm9nbmF0aGFsIGRpbWVuc2lvbnMgaW4gPGk+RHVua2xlb3N0ZXVzIHRlcnJlbGxpPC9pPiByZWxhdGl2ZSB0byBPT0wuIiklPiUNCiAgYWRkX2hlYWRlcl9hYm92ZShjKCIgIj0yLCJTbG9wZSI9NCwiSW50ZXJjZXB0Ij00KSklPiUNCiAgY29sdW1uX3NwZWMoYygzLDcpLGJvbGQ9VCklPiUNCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KVGhlIGxvZyBzbG9wZSBpcyBwb3NpdGl2ZSBmb3IgbGVuZ3RoIHZhcmlhYmxlcyB0aGF0IHNwYW4gdGhlIG92ZXJhbGwgZGltZW5zaW9ucyBvZiB0aGUgamF3IChKTTUsIGluZmVyb2duYXRoYWwgbGVuZ3RoKSwgaW5kaWNhdGluZyBwb3NpdGl2ZSBhbGxvbWV0cnkgb2YgdGhlIG1vdXRoIGFuZCBqYXdzIHJlbGF0aXZlIHRvIE9PTC4NCg0KQmVjYXVzZSB0aGVzZSByZXN1bHRzIGFyZSBiYXNlZCBvbiB0aGUgcmVncmVzc2lvbiBiZXR3ZWVuIGphdyBkaW1lbnNpb25zIGFuZCBPT0wsIGl0IGlzIHBvc3NpYmxlIHRoZXkgY291bGQgYmUgYmlhc2VkIGJ5IGFsbG9tZXRyeS4gW0FjdGlub3B0ZXJ5Z2lhbnNdKCNyYXlmaW5vbnRvZ2VueSkgYW5kIFtsYW1wcmV5c10oI2xhbXByZXlvbnRvZ2VueSkgc2hvdyBwb3NpdGl2ZSBvbnRvZ2VuZXRpYyBhbGxvbWV0cnkgb2YgT09MLCB0aG91Z2ggW3NoYXJrc10oI3NoYXJrb250b2dlbnkpIGFuZCBbdGhlIGV4dGluY3Qgc2FyY29wdGVyeWdpYW4gKkV1c3RoZW5vcHRlcm9uKl0oI0V1c3RoZW5vcHRlcm9uKSBkbyBub3QuIEJlY2F1c2Ugb2YgdGhpcyBpdCBpcyB3b3J0aCB0ZXN0aW5nIHRoZXNlIHBhdHRlcm5zIGFnYWluc3QgaGVhZCBsZW5ndGgsIHdoaWNoIGlzIGV4cGVjdGVkIHRvIHNjYWxlIGNsb3NlciB0byBpc29tZXRyeSBpbiAqRHVua2xlb3N0ZXVzIHRlcnJlbGxpKi4NCg0KYGBge3J9DQpkYXRhLmZyYW1lKCJKTTEiPWMoc3VtbWFyeShsbShsb2coSk0xKX5sb2coaGVhZF9sZW5ndGgpLGR1bmtsZW9zdGV1c19qYXdzKSkkci5zcXVhcmVkLA0KICAgICAgICAgICAgICAgICAgIHN1bW1hcnkobG0obG9nKEpNMSl+bG9nKGhlYWRfbGVuZ3RoKSxkdW5rbGVvc3RldXNfamF3cykpJGNvZWZmaWNpZW50c1sxLF0sDQogICAgICAgICAgICAgICAgICAgc3VtbWFyeShsbShsb2coSk0xKX5sb2coaGVhZF9sZW5ndGgpLGR1bmtsZW9zdGV1c19qYXdzKSkkY29lZmZpY2llbnRzWzIsXSksDQogICAgICAgICAgICJKTTIiPWMoc3VtbWFyeShsbShsb2coSk0yKX5sb2coaGVhZF9sZW5ndGgpLGR1bmtsZW9zdGV1c19qYXdzKSkkci5zcXVhcmVkLA0KICAgICAgICAgICAgICAgICAgIHN1bW1hcnkobG0obG9nKEpNMil+bG9nKGhlYWRfbGVuZ3RoKSxkdW5rbGVvc3RldXNfamF3cykpJGNvZWZmaWNpZW50c1sxLF0sDQogICAgICAgICAgICAgICAgICAgc3VtbWFyeShsbShsb2coSk0yKX5sb2coaGVhZF9sZW5ndGgpLGR1bmtsZW9zdGV1c19qYXdzKSkkY29lZmZpY2llbnRzWzIsXSksDQogICAgICAgICAgICJKTTMiPWMoc3VtbWFyeShsbShsb2coSk0zKX5sb2coaGVhZF9sZW5ndGgpLGR1bmtsZW9zdGV1c19qYXdzKSkkci5zcXVhcmVkLA0KICAgICAgICAgICAgICAgICAgIHN1bW1hcnkobG0obG9nKEpNMyl+bG9nKGhlYWRfbGVuZ3RoKSxkdW5rbGVvc3RldXNfamF3cykpJGNvZWZmaWNpZW50c1sxLF0sDQogICAgICAgICAgICAgICAgICAgc3VtbWFyeShsbShsb2coSk0zKX5sb2coaGVhZF9sZW5ndGgpLGR1bmtsZW9zdGV1c19qYXdzKSkkY29lZmZpY2llbnRzWzIsXSksDQogICAgICAgICAgICJKTTQiPWMoc3VtbWFyeShsbShsb2coSk00KX5sb2coaGVhZF9sZW5ndGgpLGR1bmtsZW9zdGV1c19qYXdzKSkkci5zcXVhcmVkLA0KICAgICAgICAgICAgICAgICAgIHN1bW1hcnkobG0obG9nKEpNNCl+bG9nKGhlYWRfbGVuZ3RoKSxkdW5rbGVvc3RldXNfamF3cykpJGNvZWZmaWNpZW50c1sxLF0sDQogICAgICAgICAgICAgICAgICAgc3VtbWFyeShsbShsb2coSk00KX5sb2coaGVhZF9sZW5ndGgpLGR1bmtsZW9zdGV1c19qYXdzKSkkY29lZmZpY2llbnRzWzIsXSksDQogICAgICAgICAgICJKTTUiPWMoc3VtbWFyeShsbShsb2coSk01KX5sb2coaGVhZF9sZW5ndGgpLGR1bmtsZW9zdGV1c19qYXdzKSkkci5zcXVhcmVkLA0KICAgICAgICAgICAgICAgICAgIHN1bW1hcnkobG0obG9nKEpNNSl+bG9nKGhlYWRfbGVuZ3RoKSxkdW5rbGVvc3RldXNfamF3cykpJGNvZWZmaWNpZW50c1sxLF0sDQogICAgICAgICAgICAgICAgICAgc3VtbWFyeShsbShsb2coSk01KX5sb2coaGVhZF9sZW5ndGgpLGR1bmtsZW9zdGV1c19qYXdzKSkkY29lZmZpY2llbnRzWzIsXSksDQogICAgICAgICAgICJJbmZlcm9nbmF0aGFsIExlbmd0aCI9DQogICAgICAgICAgICAgYyhzdW1tYXJ5KGxtKGxvZyhpbmZlcm9nbmF0aGFsX2xlbmd0aCl+bG9nKGhlYWRfbGVuZ3RoKSxkdW5rbGVvc3RldXNfamF3cykpJHIuc3F1YXJlZCwNCiAgICAgICAgICAgICAgIHN1bW1hcnkobG0obG9nKGluZmVyb2duYXRoYWxfbGVuZ3RoKX5sb2coaGVhZF9sZW5ndGgpLGR1bmtsZW9zdGV1c19qYXdzKSkkY29lZmZpY2llbnRzWzEsXSwNCiAgICAgICAgICAgICAgIHN1bW1hcnkobG0obG9nKGluZmVyb2duYXRoYWxfbGVuZ3RoKX5sb2coaGVhZF9sZW5ndGgpLGR1bmtsZW9zdGV1c19qYXdzKSkkY29lZmZpY2llbnRzWzIsXSkpJT4lDQogIHQoKSU+JQ0KICBkYXRhLmZyYW1lKHJvdy5uYW1lcz1jKCJKTTEiLCJKTTIiLCJKTTMiLCJKTTQiLCJKTTUiLCJJbmZlcm9nbmF0aGFsIExlbmd0aCIpKSU+JQ0KICBzZWxlY3QoWDEsWDYsWDcsWDgsWDksWDIsWDMsWDQsWDUpJT4lICMgTW92aW5nIHNsb3BlIHRvIGJlIGJlZm9yZSBpbnRlcmNlcHQNCiAga2FibGUoZGlnaXRzPTMsYWxpZ249ImMiLA0KICAgICAgICBjb2wubmFtZXM9YygicjIiLCJFc3RpbWF0ZSIsIlN0ZC4gRXJyb3IiLCJUIFZhbHVlIiwiUHIoPnx0fCkiLCJFc3RpbWF0ZSIsIlN0ZC4gRXJyb3IiLCJUIFZhbHVlIiwiUHIoPnx0fCkiKSwNCiAgICAgICAgY2FwdGlvbj0iRXhhbWluYXRpb24gb2YgYWxsb21ldHJ5IG9mIGluZmVyb2duYXRoYWwgZGltZW5zaW9ucyBpbiA8aT5EdW5rbGVvc3RldXMgdGVycmVsbGk8L2k+IHJlbGF0aXZlIHRvIGhlYWQgbGVuZ3RoLiIpJT4lDQogIGFkZF9oZWFkZXJfYWJvdmUoYygiICI9MiwiU2xvcGUiPTQsIkludGVyY2VwdCI9NCkpJT4lDQogIGNvbHVtbl9zcGVjKGMoMyw3KSxib2xkPVQpJT4lDQogIGthYmxlX3N0eWxpbmcoKQ0KYGBgICANCg0KU2ltaWxhciB0byB0aGUgcmVzdWx0cyB3aXRoIE9PTCwgSk01IGFuZCBpbmZlcm9nbmF0aGFsIGxlbmd0aCBib3RoIHNob3cgcG9zaXRpdmUgYWxsb21ldHJ5IHJlbGF0aXZlIHRvIGhlYWQgbGVuZ3RoLg0KDQojIyBFc3RpbWF0aW5nIHRvdGFsIGxlbmd0aCBpbiBDTU5IIDU5MzYNCg0KDQojIyMgVXNpbmcgSk0zDQoNCmBgYHtyLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KSk0zX2VzdGltYXRlczwtZHVua2xlb3N0ZXVzX2phd3MlPiUNCiAgbXV0YXRlKE9PTD1wcmVkaWN0KGZpdC5KTTMsLiksDQogICAgICAgICBjbGFkZT0iUGxhY29kZXJtaSIsc2hhcGU9ImZ1c2lmb3JtIixmYW1pbHk9IkR1bmtsZW9zdGVpZGFlIiklPiUNCiAgYXVnbWVudCh4PWZpdC5PT0wsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0aW9uIikgJT4lIA0KICBtdXRhdGUoYWNyb3NzKC5maXR0ZWQ6LnVwcGVyLH5leHAoLikqcmVncmVzc2lvbi5zdGF0cyhmaXQuT09MKSRDRiksKSU+JQ0KICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnLicpKSwgZnVucyhwYXN0ZTAoJ2ZpdDEnLCAuKSkpJT4lDQogICAgDQogIGF1Z21lbnQoeD1maXQuc3BlY2llc19hdmVyYWdlLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdGlvbiIpICU+JSANCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0LnNwZWNpZXNfYXZlcmFnZSkkQ0YpKSU+JQ0KICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnLicpKSwgZnVucyhwYXN0ZTAoJ2ZpdDInLCAuKSkpJT4lDQogICAgDQogIGF1Z21lbnQoeD1maXQuZnVzaWZvcm0sbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0aW9uIikgJT4lIA0KICBtdXRhdGUoYWNyb3NzKC5maXR0ZWQ6LnVwcGVyLH5leHAoLikqcmVncmVzc2lvbi5zdGF0cyhmaXQuZnVzaWZvcm0pJENGKSklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCdmaXQzJywgLikpKSU+JQ0KICAgIA0KICBhdWdtZW50KHg9Zml0LmZ1c2lmb3JtX2F2ZXJhZ2VzLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdGlvbiIpICU+JSANCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0LmZ1c2lmb3JtX2F2ZXJhZ2VzKSRDRikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgnZml0NCcsIC4pKSklPiUNCiAgICANCiAgYXVnbWVudCh4PWZpdC5zaGFwZV9jbGFkZTMsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0aW9uIikgJT4lIA0KICBtdXRhdGUoYWNyb3NzKC5maXR0ZWQ6LnVwcGVyLH5leHAoLikqcmVncmVzc2lvbi5zdGF0cyhmaXQuc2hhcGVfY2xhZGUzKSRDRikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgnZml0NScsIC4pKSklPiUNCiAgICANCiAgYXVnbWVudCh4PWZpdC5zaGFwZV9jbGFkZV9zcGVjaWVzX2F2ZXJhZ2VzLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdGlvbiIpICU+JSANCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlNCkkQ0YpKSU+JQ0KICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnLicpKSwgZnVucyhwYXN0ZTAoJ2ZpdDYnLCAuKSkpJT4lDQogIG11dGF0ZShzcGVjaW1lbj1kdW5rbGVvc3RldXNfamF3cyRzcGVjaW1lbiwNCiAgICAgICAgIGZpdDEuUEU9cGFzdGUwKCIoIiwNCiAgICAgICAgICAgICAgICAgICAgc3ByaW50ZigiJS4xZiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoZml0MS5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0Lk9PTCkkYWRqUEUvMTAwKSksMSkpLA0KICAgICAgICAgICAgICAgICAgICAi4oCTIiwNCiAgICAgICAgICAgICAgICAgICAgc3ByaW50ZigiJS4xZiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoZml0MS5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMoZml0Lk9PTCkkYWRqUEUvMTAwKSksMSkpLA0KICAgICAgICAgICAgICAgICAgICAiKSIpLA0KICAgICAgICAgZml0Mi5QRT1wYXN0ZTAoIigiLA0KICAgICAgICAgICAgICAgICAgICBzcHJpbnRmKCIlLjFmIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3VuZChmaXQyLmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQuc3BlY2llc19hdmVyYWdlKSRhZGpQRS8xMDApKSwxKSksDQogICAgICAgICAgICAgICAgICAgICLigJMiLA0KICAgICAgICAgICAgICAgICAgICBzcHJpbnRmKCIlLjFmIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3VuZChmaXQyLmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQuc3BlY2llc19hdmVyYWdlKSRhZGpQRS8xMDApKSwxKSksDQogICAgICAgICAgICAgICAgICAgICIpIiksDQogICAgICAgICBmaXQzLlBFPXBhc3RlMCgiKCIsDQogICAgICAgICAgICAgICAgICAgIHNwcmludGYoIiUuMWYiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKGZpdDMuZml0dGVkKigxLShyZWdyZXNzaW9uLnN0YXRzKGZpdC5mdXNpZm9ybSkkYWRqUEUvMTAwKSksMSkpLA0KICAgICAgICAgICAgICAgICAgICAi4oCTIiwNCiAgICAgICAgICAgICAgICAgICAgc3ByaW50ZigiJS4xZiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoZml0My5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMoZml0LmZ1c2lmb3JtKSRhZGpQRS8xMDApKSwxKSksDQogICAgICAgICAgICAgICAgICAgICIpIiksDQogICAgICAgICBmaXQ0LlBFPXBhc3RlMCgiKCIsDQogICAgICAgICAgICAgICAgICAgIHNwcmludGYoIiUuMWYiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKGZpdDQuZml0dGVkKigxLShyZWdyZXNzaW9uLnN0YXRzKGZpdC5mdXNpZm9ybV9hdmVyYWdlcykkYWRqUEUvMTAwKSksMSkpLA0KICAgICAgICAgICAgICAgICAgICAi4oCTIiwNCiAgICAgICAgICAgICAgICAgICAgc3ByaW50ZigiJS4xZiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoZml0NC5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMoZml0LmZ1c2lmb3JtX2F2ZXJhZ2VzKSRhZGpQRS8xMDApKSwxKSksDQogICAgICAgICAgICAgICAgICAgICIpIiksDQogICAgICAgICBmaXQ1LlBFPXBhc3RlMCgiKCIsDQogICAgICAgICAgICAgICAgICAgIHNwcmludGYoIiUuMWYiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKGZpdDUuZml0dGVkKigxLShyZWdyZXNzaW9uLnN0YXRzKGZpdC5zaGFwZV9jbGFkZTMpJGFkalBFLzEwMCkpLDEpKSwNCiAgICAgICAgICAgICAgICAgICAgIuKAkyIsDQogICAgICAgICAgICAgICAgICAgIHNwcmludGYoIiUuMWYiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKGZpdDUuZml0dGVkKigxKyhyZWdyZXNzaW9uLnN0YXRzKGZpdC5zaGFwZV9jbGFkZTMpJGFkalBFLzEwMCkpLDEpKSwNCiAgICAgICAgICAgICAgICAgICAgIikiKSwNCiAgICAgICAgIGZpdDYuUEU9cGFzdGUwKCIoIiwNCiAgICAgICAgICAgICAgICAgICAgc3ByaW50ZigiJS4xZiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoZml0Ni5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlNCkkYWRqUEUvMTAwKSksMSkpLA0KICAgICAgICAgICAgICAgICAgICAi4oCTIiwNCiAgICAgICAgICAgICAgICAgICAgc3ByaW50ZigiJS4xZiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoZml0Ni5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlNCkkYWRqUEUvMTAwKSksMSkpLA0KICAgICAgICAgICAgICAgICAgICAiKSIpLA0KICAgICAgICAgZml0MS5yYW5nZT1wYXN0ZTAoIigiLHNwcmludGYoIiUuMWYiLHJvdW5kKGZpdDEubG93ZXIsMSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIuKAkyIsc3ByaW50ZigiJS4xZiIscm91bmQoZml0MS51cHBlciwxKSksIikiKSwNCiAgICAgICAgIGZpdDIucmFuZ2U9cGFzdGUwKCIoIixzcHJpbnRmKCIlLjFmIixyb3VuZChmaXQyLmxvd2VyLDEpKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgLCLigJMiLHNwcmludGYoIiUuMWYiLHJvdW5kKGZpdDIudXBwZXIsMSkpLCIpIiksDQogICAgICAgICBmaXQzLnJhbmdlPXBhc3RlMCgiKCIsc3ByaW50ZigiJS4xZiIscm91bmQoZml0My5sb3dlciwxKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAi4oCTIixzcHJpbnRmKCIlLjFmIixyb3VuZChmaXQzLnVwcGVyLDEpKSwiKSIpLA0KICAgICAgICAgZml0NC5yYW5nZT1wYXN0ZTAoIigiLHNwcmludGYoIiUuMWYiLHJvdW5kKGZpdDQubG93ZXIsMSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIuKAkyIsc3ByaW50ZigiJS4xZiIscm91bmQoZml0NC51cHBlciwxKSksIikiKSwNCiAgICAgICAgIGZpdDUucmFuZ2U9cGFzdGUwKCIoIixzcHJpbnRmKCIlLjFmIixyb3VuZChmaXQ1Lmxvd2VyLDEpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICLigJMiLHNwcmludGYoIiUuMWYiLHJvdW5kKGZpdDUudXBwZXIsMSkpLCIpIiksDQogICAgICAgICBmaXQ2LnJhbmdlPXBhc3RlMCgiKCIsc3ByaW50ZigiJS4xZiIscm91bmQoZml0Ni5sb3dlciwxKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAi4oCTIixzcHJpbnRmKCIlLjFmIixyb3VuZChmaXQ2LnVwcGVyLDEpKSwiKSIpKSU+JQ0KICByZW1vdmVfcm93bmFtZXMoKSU+JQ0KICBjb2x1bW5fdG9fcm93bmFtZXMoInNwZWNpbWVuIiklPiUNCiAgc2VsZWN0KGZpdDEuZml0dGVkLGZpdDEuUEUsZml0MS5yYW5nZSwNCiAgICAgICAgIGZpdDIuZml0dGVkLGZpdDIuUEUsZml0Mi5yYW5nZSwNCiAgICAgICAgIGZpdDMuZml0dGVkLGZpdDMuUEUsZml0My5yYW5nZSwNCiAgICAgICAgIGZpdDQuZml0dGVkLGZpdDQuUEUsZml0NC5yYW5nZSwNCiAgICAgICAgIGZpdDUuZml0dGVkLGZpdDUuUEUsZml0NS5yYW5nZSwNCiAgICAgICAgIGZpdDYuZml0dGVkLGZpdDYuUEUsZml0Ni5yYW5nZSkNCg0KSk0zX2VzdGltYXRlcyU+JQ0KICBrYWJsZShhbGlnbj0iYyIsIGRpZ2l0cz0xLA0KICAgICAgICBjb2wubmFtZXM9YygiRXN0aW1hdGUiLCIrLy0gJVBFIiwiOTUlIEMuSS4iLA0KICAgICAgICAgICAgICAgICAgICAiRXN0aW1hdGUiLCIrLy0gJVBFIiwiOTUlIEMuSS4iLA0KICAgICAgICAgICAgICAgICAgICAiRXN0aW1hdGUiLCIrLy0gJVBFIiwiOTUlIEMuSS4iLA0KICAgICAgICAgICAgICAgICAgICAiRXN0aW1hdGUiLCIrLy0gJVBFIiwiOTUlIEMuSS4iLA0KICAgICAgICAgICAgICAgICAgICAiRXN0aW1hdGUiLCIrLy0gJVBFIiwiOTUlIEMuSS4iLA0KICAgICAgICAgICAgICAgICAgICAiRXN0aW1hdGUiLCIrLy0gJVBFIiwiOTUlIEMuSS4iKSwNCiAgICAgICAgY2FwdGlvbj0iTGVuZ3RoIGVzdGltYXRlcyBvZiB0aGUgbGFyZ2VzdCBrbm93biBzcGVjaW1lbiBvZiA8aT5EdW5rbGVvc3RldXMgdGVycmVsbGk8L2k+IChDTU5IIDU5MzYpIHVzaW5nIEpNMyAoaGVpZ2h0IG9mIHRoZSBpbmZlcm9nbmF0aGFsIGF0IHBvc3RlcmlvciBjdXNwKS4gRXN0aW1hdGVzIGNhbGN1bGF0ZWQgYnkgZXN0aW1hdGluZyBPT0wgdXNpbmcgdGhlIHJlZ3Jlc3Npb24gYmV0d2VlbiBKTTMgYW5kIE9PTCwgYW5kIHRoZW4gaW5zZXJ0aW5nIHRoYXQgdmFsdWUgaW50byB0aGUgcmVzcGVjdGl2ZSBlcXVhdGlvbiBmb3IgdG90YWwgbGVuZ3RoLiBBbGwgbWVhc3VyZW1lbnRzIGluIGNtLiIpJT4lDQogIGFkZF9oZWFkZXJfYWJvdmUoYygiICI9MSwiQWxsIHNwZWNpbWVucyI9MywiU3BlY2llcyBhdmVyYWdlcyI9MywNCiAgICAgICAgICAgICAgICAgICAgICJBbGwgc3BlY2ltZW5zIj0zLCJTcGVjaWVzIGF2ZXJhZ2VzIj0zLA0KICAgICAgICAgICAgICAgICAgICAgIkFsbCBzcGVjaW1lbnMiPTMsIlNwZWNpZXMgYXZlcmFnZXMiPTMpKSU+JQ0KICBhZGRfaGVhZGVyX2Fib3ZlKGMoIiAiPTEsIkFsbCB0YXhhIj02LCJGdXNpZm9ybSBmaXNoZXMiPTYsIldpdGggdmFyaWFibGUgc2xvcGVzIGZvciBDaG9uZHJpY2h0aHllcyI9NikpJT4lDQogIGNvbHVtbl9zcGVjKGMoMiw1LDgsMTEsMTQsMTcpLGJvbGQ9VCklPiUNCiAga2FibGVfc3R5bGluZygpJT4lDQogIHNjcm9sbF9ib3god2lkdGggPSAiMTAwJSIpDQpgYGANCg0KIyMjIFVzaW5nIEpNNQ0KDQpgYGB7cixjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30NCkpNNV9lc3RpbWF0ZXM8LWR1bmtsZW9zdGV1c19qYXdzJT4lDQogIG11dGF0ZShPT0w9cHJlZGljdChmaXQuSk01LC4pLA0KICAgICAgICAgY2xhZGU9IlBsYWNvZGVybWkiLHNoYXBlPSJmdXNpZm9ybSIsZmFtaWx5PSJEdW5rbGVvc3RlaWRhZSIpJT4lDQogIGF1Z21lbnQoeD1maXQuT09MLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdGlvbiIpICU+JSANCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0Lk9PTCkkQ0YpLCklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCdmaXQxJywgLikpKSU+JQ0KICAgIA0KICBhdWdtZW50KHg9Zml0LnNwZWNpZXNfYXZlcmFnZSxuZXdkYXRhPS4saW50ZXJ2YWw9InByZWRpY3Rpb24iKSAlPiUgDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5zcGVjaWVzX2F2ZXJhZ2UpJENGKSklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCdmaXQyJywgLikpKSU+JQ0KICAgIA0KICBhdWdtZW50KHg9Zml0LmZ1c2lmb3JtLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdGlvbiIpICU+JSANCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0LmZ1c2lmb3JtKSRDRikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgnZml0MycsIC4pKSklPiUNCiAgICANCiAgYXVnbWVudCh4PWZpdC5mdXNpZm9ybV9hdmVyYWdlcyxuZXdkYXRhPS4saW50ZXJ2YWw9InByZWRpY3Rpb24iKSAlPiUgDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5mdXNpZm9ybV9hdmVyYWdlcykkQ0YpKSU+JQ0KICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnLicpKSwgZnVucyhwYXN0ZTAoJ2ZpdDQnLCAuKSkpJT4lDQogICAgDQogIGF1Z21lbnQoeD1maXQuc2hhcGVfY2xhZGUzLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdGlvbiIpICU+JSANCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlMykkQ0YpKSU+JQ0KICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnLicpKSwgZnVucyhwYXN0ZTAoJ2ZpdDUnLCAuKSkpJT4lDQogICAgDQogIGF1Z21lbnQoeD1maXQuc2hhcGVfY2xhZGVfc3BlY2llc19hdmVyYWdlcyxuZXdkYXRhPS4saW50ZXJ2YWw9InByZWRpY3Rpb24iKSAlPiUgDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5zaGFwZV9jbGFkZTQpJENGKSklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCdmaXQ2JywgLikpKSU+JQ0KICBtdXRhdGUoc3BlY2ltZW49ZHVua2xlb3N0ZXVzX2phd3Mkc3BlY2ltZW4sDQogICAgICAgICBmaXQxLlBFPXBhc3RlMCgiKCIsDQogICAgICAgICAgICAgICAgICAgIHNwcmludGYoIiUuMWYiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKGZpdDEuZml0dGVkKigxLShyZWdyZXNzaW9uLnN0YXRzKGZpdC5PT0wpJGFkalBFLzEwMCkpLDEpKSwNCiAgICAgICAgICAgICAgICAgICAgIuKAkyIsDQogICAgICAgICAgICAgICAgICAgIHNwcmludGYoIiUuMWYiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKGZpdDEuZml0dGVkKigxKyhyZWdyZXNzaW9uLnN0YXRzKGZpdC5PT0wpJGFkalBFLzEwMCkpLDEpKSwNCiAgICAgICAgICAgICAgICAgICAgIikiKSwNCiAgICAgICAgIGZpdDIuUEU9cGFzdGUwKCIoIiwNCiAgICAgICAgICAgICAgICAgICAgc3ByaW50ZigiJS4xZiIsDQogICAgICAgICAgICAgICAgICAgICAgICByb3VuZChmaXQyLmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQuc3BlY2llc19hdmVyYWdlKSRhZGpQRS8xMDApKSwxKSksDQogICAgICAgICAgICAgICAgICAgICLigJMiLA0KICAgICAgICAgICAgICAgICAgICBzcHJpbnRmKCIlLjFmIiwNCiAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKGZpdDIuZml0dGVkKigxKyhyZWdyZXNzaW9uLnN0YXRzKGZpdC5zcGVjaWVzX2F2ZXJhZ2UpJGFkalBFLzEwMCkpLDEpKSwNCiAgICAgICAgICAgICAgICAgICAgIikiKSwNCiAgICAgICAgIGZpdDMuUEU9cGFzdGUwKCIoIiwNCiAgICAgICAgICAgICAgICAgICAgc3ByaW50ZigiJS4xZiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoZml0My5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LmZ1c2lmb3JtKSRhZGpQRS8xMDApKSwxKSksDQogICAgICAgICAgICAgICAgICAgICLigJMiLA0KICAgICAgICAgICAgICAgICAgICBzcHJpbnRmKCIlLjFmIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3VuZChmaXQzLmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQuZnVzaWZvcm0pJGFkalBFLzEwMCkpLDEpKSwNCiAgICAgICAgICAgICAgICAgICAgIikiKSwNCiAgICAgICAgIGZpdDQuUEU9cGFzdGUwKCIoIiwNCiAgICAgICAgICAgICAgICAgICAgc3ByaW50ZigiJS4xZiIsDQogICAgICAgICAgICAgICAgICAgICAgcm91bmQoZml0NC5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LmZ1c2lmb3JtX2F2ZXJhZ2VzKSRhZGpQRS8xMDApKSwxKSksDQogICAgICAgICAgICAgICAgICAgICLigJMiLA0KICAgICAgICAgICAgICAgICAgICBzcHJpbnRmKCIlLjFmIiwNCiAgICAgICAgICAgICAgICAgICAgICByb3VuZChmaXQ0LmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQuZnVzaWZvcm1fYXZlcmFnZXMpJGFkalBFLzEwMCkpLDEpKSwNCiAgICAgICAgICAgICAgICAgICAgIikiKSwNCiAgICAgICAgIGZpdDUuUEU9cGFzdGUwKCIoIiwNCiAgICAgICAgICAgICAgICAgICAgc3ByaW50ZigiJS4xZiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoZml0NS5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlMykkYWRqUEUvMTAwKSksMSkpLA0KICAgICAgICAgICAgICAgICAgICAi4oCTIiwNCiAgICAgICAgICAgICAgICAgICAgc3ByaW50ZigiJS4xZiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoZml0NS5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlMykkYWRqUEUvMTAwKSksMSkpLA0KICAgICAgICAgICAgICAgICAgICAiKSIpLA0KICAgICAgICAgZml0Ni5QRT1wYXN0ZTAoIigiLA0KICAgICAgICAgICAgICAgICAgICBzcHJpbnRmKCIlLjFmIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3VuZChmaXQ2LmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQuc2hhcGVfY2xhZGU0KSRhZGpQRS8xMDApKSwxKSksDQogICAgICAgICAgICAgICAgICAgICLigJMiLA0KICAgICAgICAgICAgICAgICAgICBzcHJpbnRmKCIlLjFmIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3VuZChmaXQ2LmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQuc2hhcGVfY2xhZGU0KSRhZGpQRS8xMDApKSwxKSksDQogICAgICAgICAgICAgICAgICAgICIpIiksDQogICAgICAgICBmaXQxLnJhbmdlPXBhc3RlMCgiKCIsc3ByaW50ZigiJS4xZiIscm91bmQoZml0MS5sb3dlciwxKSksIuKAkyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBzcHJpbnRmKCIlLjFmIixyb3VuZChmaXQxLnVwcGVyLDEpKSwiKSIpLA0KICAgICAgICAgZml0Mi5yYW5nZT1wYXN0ZTAoIigiLHNwcmludGYoIiUuMWYiLHJvdW5kKGZpdDIubG93ZXIsMSkpLCLigJMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgc3ByaW50ZigiJS4xZiIscm91bmQoZml0Mi51cHBlciwxKSksIikiKSwNCiAgICAgICAgIGZpdDMucmFuZ2U9cGFzdGUwKCIoIixzcHJpbnRmKCIlLjFmIixyb3VuZChmaXQzLmxvd2VyLDEpKSwi4oCTIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwcmludGYoIiUuMWYiLHJvdW5kKGZpdDMudXBwZXIsMSkpLCIpIiksDQogICAgICAgICBmaXQ0LnJhbmdlPXBhc3RlMCgiKCIsc3ByaW50ZigiJS4xZiIscm91bmQoZml0NC5sb3dlciwxKSksIuKAkyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBzcHJpbnRmKCIlLjFmIixyb3VuZChmaXQ0LnVwcGVyLDEpKSwiKSIpLA0KICAgICAgICAgZml0NS5yYW5nZT1wYXN0ZTAoIigiLHNwcmludGYoIiUuMWYiLHJvdW5kKGZpdDUubG93ZXIsMSkpLCLigJMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgc3ByaW50ZigiJS4xZiIscm91bmQoZml0NS51cHBlciwxKSksIikiKSwNCiAgICAgICAgIGZpdDYucmFuZ2U9cGFzdGUwKCIoIixzcHJpbnRmKCIlLjFmIixyb3VuZChmaXQ2Lmxvd2VyLDEpKSwi4oCTIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwcmludGYoIiUuMWYiLHJvdW5kKGZpdDYudXBwZXIsMSkpLCIpIikpJT4lDQogIHJlbW92ZV9yb3duYW1lcygpJT4lDQogIGNvbHVtbl90b19yb3duYW1lcygic3BlY2ltZW4iKSU+JQ0KICBzZWxlY3QoZml0MS5maXR0ZWQsZml0MS5QRSxmaXQxLnJhbmdlLA0KICAgICAgICAgZml0Mi5maXR0ZWQsZml0Mi5QRSxmaXQyLnJhbmdlLA0KICAgICAgICAgZml0My5maXR0ZWQsZml0My5QRSxmaXQzLnJhbmdlLA0KICAgICAgICAgZml0NC5maXR0ZWQsZml0NC5QRSxmaXQ0LnJhbmdlLA0KICAgICAgICAgZml0NS5maXR0ZWQsZml0NS5QRSxmaXQ1LnJhbmdlLA0KICAgICAgICAgZml0Ni5maXR0ZWQsZml0Ni5QRSxmaXQ2LnJhbmdlKQ0KDQpKTTVfZXN0aW1hdGVzJT4lDQogIGthYmxlKGFsaWduPSJjIiwgZGlnaXRzPTEsDQogICAgICAgIGNvbC5uYW1lcz1jKCJFc3RpbWF0ZSIsIisvLSAlUEUiLCI5NSUgQy5JLiIsDQogICAgICAgICAgICAgICAgICAgICJFc3RpbWF0ZSIsIisvLSAlUEUiLCI5NSUgQy5JLiIsDQogICAgICAgICAgICAgICAgICAgICJFc3RpbWF0ZSIsIisvLSAlUEUiLCI5NSUgQy5JLiIsDQogICAgICAgICAgICAgICAgICAgICJFc3RpbWF0ZSIsIisvLSAlUEUiLCI5NSUgQy5JLiIsDQogICAgICAgICAgICAgICAgICAgICJFc3RpbWF0ZSIsIisvLSAlUEUiLCI5NSUgQy5JLiIsDQogICAgICAgICAgICAgICAgICAgICJFc3RpbWF0ZSIsIisvLSAlUEUiLCI5NSUgQy5JLiIpLA0KICAgICAgICBjYXB0aW9uPSJMZW5ndGggZXN0aW1hdGVzIG9mIHRoZSBsYXJnZXN0IGtub3duIHNwZWNpbWVuIG9mIDxpPkR1bmtsZW9zdGV1cyB0ZXJyZWxsaTwvaT4gKENNTkggNTkzNikgdXNpbmcgSk01IChsZW5ndGggb2YgdGhlIGJpdGluZyBwb3J0aW9uIG9mIHRoZSBpbmZlcm9nbmF0aGFsKS4gRXN0aW1hdGVzIGNhbGN1bGF0ZWQgYnkgZXN0aW1hdGluZyBPT0wgdXNpbmcgdGhlIHJlZ3Jlc3Npb24gYmV0d2VlbiBKTTUgYW5kIE9PTCwgYW5kIHRoZW4gaW5zZXJ0aW5nIHRoYXQgdmFsdWUgaW50byB0aGUgcmVzcGVjdGl2ZSBlcXVhdGlvbiBmb3IgdG90YWwgbGVuZ3RoLiBBbGwgbWVhc3VyZW1lbnRzIGluIGNtLiIpJT4lDQogIGFkZF9oZWFkZXJfYWJvdmUoYygiICI9MSwiQWxsIHNwZWNpbWVucyI9MywiU3BlY2llcyBhdmVyYWdlcyI9MywNCiAgICAgICAgICAgICAgICAgICAgICJBbGwgc3BlY2ltZW5zIj0zLCJTcGVjaWVzIGF2ZXJhZ2VzIj0zLA0KICAgICAgICAgICAgICAgICAgICAgIkFsbCBzcGVjaW1lbnMiPTMsIlNwZWNpZXMgYXZlcmFnZXMiPTMpKSU+JQ0KICBhZGRfaGVhZGVyX2Fib3ZlKGMoIiAiPTEsIkFsbCB0YXhhIj02LCJGdXNpZm9ybSBmaXNoZXMiPTYsIldpdGggdmFyaWFibGUgc2xvcGVzIGZvciBDaG9uZHJpY2h0aHllcyI9NikpJT4lDQogIGNvbHVtbl9zcGVjKGMoMiw1LDgsMTEsMTQsMTcpLGJvbGQ9VCklPiUNCiAga2FibGVfc3R5bGluZygpJT4lDQogIHNjcm9sbF9ib3god2lkdGggPSAiMTAwJSIpDQpgYGANCg0KYGBge3J9DQpjYmluZCgNCiAgSk0zX2VzdGltYXRlcyU+JQ0KICBzbGljZSgxKSU+JQ0KICBtdXRhdGUoYWNyb3NzKHdoZXJlKGlzLm51bWVyaWMpLCB+YXMuY2hhcmFjdGVyKHNwcmludGYoIiUuMWYiLCByb3VuZCguLDEpKSkpKSAlPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJ2ZpdDEuJykpLCBmdW5zKGdzdWIoJ2ZpdDEuJywnSk0zX0FsbCBzcGVjaW1lbnNfSW5kaXZpZHVhbCBEYXRhXycsIC4pKSklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJ2ZpdDIuJykpLCBmdW5zKGdzdWIoJ2ZpdDIuJywnSk0zX0FsbCBzcGVjaW1lbnNfU3BlY2llcyBBdmVyYWdlc18nLCAuKSkpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCdmaXQzLicpKSwgZnVucyhnc3ViKCdmaXQzLicsJ0pNM19GdXNpZm9ybSBmaXNoZXMgb25seV9JbmRpdmlkdWFsIERhdGFfJywgLikpKSU+JQ0KICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnZml0NC4nKSksIGZ1bnMoZ3N1YignZml0NC4nLCdKTTNfRnVzaWZvcm0gZmlzaGVzIG9ubHlfU3BlY2llcyBBdmVyYWdlc18nLCAuKSkpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCdmaXQ1LicpKSwgZnVucyhnc3ViKCdmaXQ1LicsJ0pNM19WYXJpYWJsZSBzbG9wZSBmb3IgY2hvbmRyaWNodGh5YW5zX0luZGl2aWR1YWwgRGF0YV8nLCAuKSkpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCdmaXQ2LicpKSwgZnVucyhnc3ViKCdmaXQ2LicsJ0pNM19WYXJpYWJsZSBzbG9wZSBmb3IgY2hvbmRyaWNodGh5YW5zX1NwZWNpZXMgQXZlcmFnZXNfJywgLikpKSwNCiAgSk01X2VzdGltYXRlcyU+JQ0KICAgIHNsaWNlKDEpJT4lDQogICAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgfmFzLmNoYXJhY3RlcihzcHJpbnRmKCIlLjFmIiwgcm91bmQoLiwxKSkpKSkgJT4lDQogICAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJ2ZpdDEuJykpLCBmdW5zKGdzdWIoJ2ZpdDEuJywnSk01X0FsbCBzcGVjaW1lbnNfSW5kaXZpZHVhbCBEYXRhXycsIC4pKSklPiUNCiAgICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnZml0Mi4nKSksIGZ1bnMoZ3N1YignZml0Mi4nLCdKTTVfQWxsIHNwZWNpbWVuc19TcGVjaWVzIEF2ZXJhZ2VzXycsIC4pKSklPiUNCiAgICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnZml0My4nKSksIGZ1bnMoZ3N1YignZml0My4nLCdKTTVfRnVzaWZvcm0gZmlzaGVzIG9ubHlfSW5kaXZpZHVhbCBEYXRhXycsIC4pKSklPiUNCiAgICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnZml0NC4nKSksIGZ1bnMoZ3N1YignZml0NC4nLCdKTTVfRnVzaWZvcm0gZmlzaGVzIG9ubHlfU3BlY2llcyBBdmVyYWdlc18nLCAuKSkpJT4lDQogICAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJ2ZpdDUuJykpLCBmdW5zKGdzdWIoJ2ZpdDUuJywnSk01X1ZhcmlhYmxlIHNsb3BlIGZvciBjaG9uZHJpY2h0aHlhbnNfSW5kaXZpZHVhbCBEYXRhXycsIC4pKSklPiUNCiAgICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnZml0Ni4nKSksIGZ1bnMoZ3N1YignZml0Ni4nLCdKTTVfVmFyaWFibGUgc2xvcGUgZm9yIGNob25kcmljaHRoeWFuc19TcGVjaWVzIEF2ZXJhZ2VzXycsIC4pKSkNCiklPiUNCiAgcGl2b3RfbG9uZ2VyKGRhdGE9LiwNCiAgICAgICAgICAgICAgIGNvbHM9ZXZlcnl0aGluZygpLA0KICAgICAgICAgICAgICAgbmFtZXNfdG8gPSBjKCJtZWFzdXJlbWVudCIsIm1vZGVsIiwidHlwZSIsImRhdGFfdmFsdWUiKSwNCiAgICAgICAgICAgICAgIG5hbWVzX3NlcCA9ICJfIiklPiUNCiAgbXV0YXRlKHRvdGFsPXBhc3RlKG1lYXN1cmVtZW50LG1vZGVsLHR5cGUsc2VwPSJfIiksDQogICAgICAgICBuYW1lPXJlcChjKCJFc3QuIiwiKy8tIFBFIiwiOTUlIFAuSS4iKSwxMikpJT4lDQogIHNlbGVjdCh0b3RhbCx2YWx1ZSxuYW1lKSU+JQ0KICBwaXZvdF93aWRlcihpZF9jb2xzPSJ0b3RhbCIsDQogICAgICAgICAgICAgIG5hbWVzX2Zyb209Im5hbWUiKSU+JQ0KICBzZXBhcmF0ZShjb2w9dG90YWwsDQogICAgICAgICAgIGludG89YygiTWVhc3VyZW1lbnQiLCJNb2RlbCIsIlR5cGUiKSwNCiAgICAgICAgICAgc2VwPSJfIiklPiUNCiAgd3JpdGUueGxzeCguLCJEZXZvbmlhbiBGaXNoIFRhbGUgVGFibGUgNCAoQ01OSCA1OTM2KS54bHN4IikNCmBgYA0KDQpgYGB7cn0NCmNiaW5kKA0KICBKTTNfZXN0aW1hdGVzICU+JSBzbGljZSgxKSAlPiUgcmVuYW1lX3dpdGgofnN0cl9jKCJKTTNfIiwuKSwuY29scz1ldmVyeXRoaW5nKCkpLA0KICBKTTVfZXN0aW1hdGVzICU+JSBzbGljZSgxKSAlPiUgcmVuYW1lX3dpdGgofnN0cl9jKCJKTTVfIiwuKSwuY29scz1ldmVyeXRoaW5nKCkpKSU+JQ0KICBzZWxlY3QoZW5kc193aXRoKCIuZml0dGVkIikpJT4lDQogIGdhdGhlcigpJT4lDQogIHN1bW1hcmlzZShtZWFuPW1lYW4odmFsdWUpLG1lZGlhbj1tZWRpYW4odmFsdWUpKSU+JQ0KICBrYWJsZShkaWdpdHM9MSwNCiAgICAgICAgY29sLm5hbWVzPWMoIk1lYW4iLCJNZWRpYW4iKSwNCiAgICAgICAgY2FwdGlvbj0iTWVhbiBhbmQgbWVkaWFuIGxlbmd0aCBlc3RpbWF0ZXMgZm9yIHRoZSBsYXJnZXN0IGtub3duIHNwZWNpbWVuIG9mIDxpPkR1bmtsZW9zdGV1cyB0ZXJyZWxsaTwvaT4gKENNTkggNTkzNikiLA0KICAgICAgICB0YWJsZS5hdHRyID0gInN0eWxlPSd3aWR0aDo3MCU7JyIpJT4lDQogIGthYmxlX3N0eWxpbmcoKQ0KYGBgDQoNCiMjIFByZWRpY3RlZCBsZW5ndGhzIGZvciBDTU5IIDU5MzYgdXNpbmcgb3RoZXIgamF3IG1lYXN1cmVtZW50cw0KDQpgYGB7cn0NCkNNTkhfNTkzNl9lc3RpbWF0ZXM8LWR1bmtsZW9zdGV1c19qYXdzICU+JQ0KICBmaWx0ZXIoc3BlY2ltZW49PSJDTU5IIDU5MzYiKSAlPiUNCiAgc2VsZWN0KEpNMTpKTTUpJT4lDQogIG11dGF0ZShzaGFwZT0iZnVzaWZvcm0iLGNsYWRlPSJQbGFjb2Rlcm1pIixmYW1pbHk9IkR1bmtsZW9zdGVpZGFlIiwNCiAgICAgICAgIGluZmVyb2duYXRoYWxfbGVuZ3RoPXByZWRpY3QobG0oaW5mZXJvZ25hdGhhbF9sZW5ndGh+Sk01LGR1bmtsZW9zdGV1c19qYXdzKSxuZXdkYXRhPS4pKSU+JQ0KICBhdWdtZW50KGxtKE9PTH5KTTEsZHVua2xlb3N0ZXVzX2phd3MpLG5ld2RhdGE9LikgJT4lDQogIHJlbmFtZShKTTEuZml0dGVkPS5maXR0ZWQpJT4lDQogIGF1Z21lbnQobG0oT09MfkpNMixkdW5rbGVvc3RldXNfamF3cyksbmV3ZGF0YT0uKSAlPiUNCiAgcmVuYW1lKEpNMi5maXR0ZWQ9LmZpdHRlZCklPiUNCiAgYXVnbWVudChmaXQuSk0zLG5ld2RhdGE9LikgJT4lDQogIHJlbmFtZShKTTMuZml0dGVkPS5maXR0ZWQpJT4lDQogIGF1Z21lbnQobG0oT09MfkpNNCxkdW5rbGVvc3RldXNfamF3cyksbmV3ZGF0YT0uKSAlPiUNCiAgcmVuYW1lKEpNNC5maXR0ZWQ9LmZpdHRlZCklPiUNCiAgYXVnbWVudChmaXQuSk01LG5ld2RhdGE9LikgJT4lDQogIHJlbmFtZShKTTUuZml0dGVkPS5maXR0ZWQpJT4lDQogIGF1Z21lbnQobG0oT09MfmluZmVyb2duYXRoYWxfbGVuZ3RoLGR1bmtsZW9zdGV1c19qYXdzKSxuZXdkYXRhPS4pICU+JQ0KICByZW5hbWUoaW5mZXJvZ25hdGhhbF9sZW5ndGguZml0dGVkPS5maXR0ZWQpDQoNCkNNTkhfNTkzNl9lc3RpbWF0ZXM8LUNNTkhfNTkzNl9lc3RpbWF0ZXMgJT4lDQogIG11dGF0ZShsZW5ndGhfSk0xPWV4cChwcmVkaWN0KGZpdC5zaGFwZV9jbGFkZTMsbmV3ZGF0YT1DTU5IXzU5MzZfZXN0aW1hdGVzJT4lcmVuYW1lKE9PTD1KTTEuZml0dGVkKSkpKnJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlMykkQ0YsDQogICAgICAgICBsZW5ndGhfSk0yPWV4cChwcmVkaWN0KGZpdC5zaGFwZV9jbGFkZTMsbmV3ZGF0YT1DTU5IXzU5MzZfZXN0aW1hdGVzJT4lcmVuYW1lKE9PTD1KTTIuZml0dGVkKSkpKnJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlMykkQ0YsDQogICAgICAgICBsZW5ndGhfSk0zPWV4cChwcmVkaWN0KGZpdC5zaGFwZV9jbGFkZTMsbmV3ZGF0YT1DTU5IXzU5MzZfZXN0aW1hdGVzJT4lcmVuYW1lKE9PTD1KTTMuZml0dGVkKSkpKnJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlMykkQ0YsDQogICAgICAgICBsZW5ndGhfSk00PWV4cChwcmVkaWN0KGZpdC5zaGFwZV9jbGFkZTMsbmV3ZGF0YT1DTU5IXzU5MzZfZXN0aW1hdGVzJT4lcmVuYW1lKE9PTD1KTTQuZml0dGVkKSkpKnJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlMykkQ0YsDQogICAgICAgICBsZW5ndGhfSk01PWV4cChwcmVkaWN0KGZpdC5zaGFwZV9jbGFkZTMsbmV3ZGF0YT1DTU5IXzU5MzZfZXN0aW1hdGVzJT4lcmVuYW1lKE9PTD1KTTUuZml0dGVkKSkpKnJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlMykkQ0YsDQogICAgICAgICBsZW5ndGhfaW5mPWV4cChwcmVkaWN0KGZpdC5zaGFwZV9jbGFkZTMsbmV3ZGF0YT1DTU5IXzU5MzZfZXN0aW1hdGVzJT4lcmVuYW1lKE9PTD1pbmZlcm9nbmF0aGFsX2xlbmd0aC5maXR0ZWQpKSkqcmVncmVzc2lvbi5zdGF0cyhmaXQuc2hhcGVfY2xhZGUzKSRDRg0KICAgICAgICAgKQ0KDQpjYmluZChDTU5IXzU5MzZfZXN0aW1hdGVzICU+JSBzZWxlY3QoSk0xOkpNNSxpbmZlcm9nbmF0aGFsX2xlbmd0aCkgJT4lIHJlbmFtZSAoIkluZmVyb2duYXRoYWwgTGVuZ3RoIj1pbmZlcm9nbmF0aGFsX2xlbmd0aCkgJT4ldCgpLA0KICBDTU5IXzU5MzZfZXN0aW1hdGVzICU+JSBzZWxlY3QoSk0xLmZpdHRlZDppbmZlcm9nbmF0aGFsX2xlbmd0aC5maXR0ZWQpICU+JSB0KCksDQogIENNTkhfNTkzNl9lc3RpbWF0ZXMgJT4lIHNlbGVjdChsZW5ndGhfSk0xOmxlbmd0aF9pbmYpICU+JSB0KCkpICU+JQ0KICBrYWJsZShjb2wubmFtZXM9YygiSW5mZXJvZ25hdGhhbCBNZWFzdXJlbWVudCIsIkVzdGltYXRlZCBPT0wiLCJFc3RpbWF0ZWQgVG90YWwgTGVuZ3RoIiksZGlnaXRzPTEsDQogICAgICAgIGFsaWduPSJjIiwNCiAgICAgICAgY2FwdGlvbj0iRXN0aW1hdGVkIGxlbmd0aHMgZm9yIENNTkggNTkzNiB1c2luZyBhbGwgamF3IG1lYXN1cmVtZW50cyBjb25zaWRlcmVkLiBJbmZlcm9nbmF0aGFsIGxlbmd0aCB3YXMgZXN0aW1hdGVkIGJhc2VkIG9uIGFsbG9tZXRyaWMgcmVsYXRpb25zaGlwIHdpdGggSk01IGluIHNhbXBsZSBvZiA8aT5EdW5rbGVvc3RldXMgdGVycmVsbGk8L2k+LCBhcyB0aGUgb3JhbCByZWdpb24gb2YgdGhlIGluZmVyb2duYXRoYWwgaXMgY29uc2lzdGVudGx5IGFyb3VuZCA1MCUgb2YgdG90YWwgaW5mZXJvZ25hdGhhbCBsZW5ndGggaW4gPGk+RHVua2xlb3N0ZXVzPC9pPi4gTGVuZ3RoIGVzdGltYXRlZCBmcm9tIE9PTCB1c2luZyB0aGUgZXF1YXRpb24gd2l0aCBzaGFwZSBhbmQgYSBzZXBhcmF0ZSBhbGxvbWV0cmljIGxpbmUgZm9yIHNoYXJrcy4gQWxsIG1lYXN1cmVtZW50cyBpbiBjbS4iKSAlPiUNCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KQmFzZWQgb24gdGhpcyBub25lIG9mIHRoZSBpbmZlcm9nbmF0aGFsIG1lYXN1cmVtZW50cyBhcHBlYXIgdG8gcHJvZHVjZSBzaXplcyBvZiA1KyBtIGZvciB0aGUgbGFyZ2VzdCBpbmRpdmlkdWFscyBvZiAqRHVua2xlb3N0ZXVzIHRlcnJlbGxpKiwgd2l0aCBtb3N0IHByb2R1Y2luZyBlc3RpbWF0ZXMgYXJvdW5kIDMuOS00LjEgbS4NCg0KIyBDb21wYXJpbmcgc3VwcG9ydCBzdGF0aXN0aWNzIGFjcm9zcyBtb2RlbHMNCg0KYGBge3IsY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZScsY2FjaGU9VFJVRSxjYWNoZS5leHRyYSA9IHRvb2xzOjptZDVzdW0oIkRldm9uaWFuIEZpc2ggVGFsZSBTdXBwbGVtZW50YXJ5IEZpbGUgMSAoRGF0YSkueGxzeCIpfQ0KZXF1YXRpb25zPC1zYXBwbHkoYygiZml0Lk9PTCIsImZpdC5ub19leHRyZW1lX3NoYXBlcyIsImZpdC53aXRoX3NoYXBlIiwiZml0LmZ1c2lmb3JtIiwNCiAgICAgICAgICAgICAgICAgICAgImZpdC5yZWxhdGl2ZWRlcHRoNCIsIndpdGhfc25vdXRfbGVuZ3RoIiwiZml0Lm1pbnVzX3Nub3V0X2xlbmd0aDEiLA0KICAgICAgICAgICAgICAgICAgICAiZml0LnBlbGFnaWMiLCJmaXQubm9fYWNhbnRob3B0ZXJ5Z2lpMiIsImZpdC5zaGFya3MiLA0KICAgICAgICAgICAgICAgICAgICAiZml0LnNoYXBlX2NsYWRlIiwiZml0LnNoYXBlX2NsYWRlMyIsDQogICAgICAgICAiZml0LnNwZWNpZXNfYXZlcmFnZSIsImZpdC5zcGVjaWVzX2F2ZXJhZ2UyIiwiZml0LndpdGhfc2hhcGVfYXZlcmFnZXMiLA0KICAgICAgICAgImZpdC5mdXNpZm9ybV9hdmVyYWdlcyIsImZpdC5yZWxhdGl2ZWRlcHRoX2F2ZXJhZ2VzIiwiZml0Lm1pbnVzX3Nub3V0X2xlbmd0aDIiLA0KICAgICAgICAgImZpdC5wZWxhZ2ljX2F2ZXJhZ2VzIiwiZml0Lm5vX2FjYW50aG9wdGVyeWdpaTJfYXZlcmFnZXMiLCJmaXQuc2hhcmtzX2F2ZXJhZ2VzIiwNCiAgICAgICAgICJmaXQuc2hhcGVfY2xhZGVfc3BlY2llc19hdmVyYWdlcyIpLCBmdW5jdGlvbih4KXJlZ3Jlc3Npb24uc3RhdHMoZ2V0KHgpKSklPiUNCiAgdCgpJT4lYXMuZGF0YS5mcmFtZSgpJT4lDQogIHJvd25hbWVzX3RvX2NvbHVtbigpJT4lDQogIG11dGF0ZShyb3c9MTpucm93KC4pKQ0KZXF1YXRpb25zJT4lDQogIG11dGF0ZShtb2RlbCA9IGlmZWxzZShyb3cgPA0KICAgICAgICAgICAgICAgICAgICAgICAgICAoZXF1YXRpb25zJT4lZmlsdGVyKHJvd25hbWU9PSJmaXQuc3BlY2llc19hdmVyYWdlIiklPiVwdWxsKHJvdykpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICJpbmRpdmlkdWFsIHZhbHVlcyIsInNwZWNpZXMgYXZlcmFnZXMiKSklPiUNCiAgc2VsZWN0KHJvd25hbWUsbW9kZWwsZXZlcnl0aGluZygpKSU+JQ0KICBzZWxlY3QoLXJvdyklPiUNCiAga2FibGUoKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCklPiUNCiAgc2Nyb2xsX2JveCh3aWR0aCA9ICIxMDAlIikNCg0KIyBXcml0aW5nIG1vZGVsIHN0YXRpc3RpY3MgZm9yIG1ham9yIG1vZGVscyBpbiBhIHNpbmdsZSwgZWFzaWx5IHJlYWRhYmxlIHRhYmxlLCBpLmUuLCBUYWJsZSAxIG9mIHRoZSBtYW51c2NyaXB0DQoobW9kZWxfc3RhdHM8LXJiaW5kKA0KICBmaXQuT09MJSQlDQogICAgY2JpbmQobW9kZWw9IkFsbCBzcGVjaWVzIiwNCiAgICAgICAgICBlcXVhdGlvbj1wYXN0ZTAoIkxuKFRMKSA9ICIscm91bmQoLiRjb2VmZmljaWVudHNbMl0sNCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICIgKiBMbihPT0wpICIsaWZlbHNlKC4kY29lZmZpY2llbnRzWzFdPjEsIisiLCItIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICIgIixhYnMocm91bmQoLiRjb2VmZmljaWVudHNbMV0sNCkpKSwNCiAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKC4pKSwNCiAgZml0Lm5vX2V4dHJlbWVfc2hhcGVzJSQlDQogICAgY2JpbmQobW9kZWw9IkZ1c2lmb3JtIGFuZCBlbG9uZ2F0ZSB0YXhhIiwNCiAgICAgICAgICBlcXVhdGlvbj1wYXN0ZTAoIkxuKFRMKSA9ICIscm91bmQoLiRjb2VmZmljaWVudHNbMl0sNCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICIgKiBMbihPT0wpICIsaWZlbHNlKC4kY29lZmZpY2llbnRzWzFdPjEsIisiLCItIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICIgIixhYnMocm91bmQoLiRjb2VmZmljaWVudHNbMV0sNCkpKSwNCiAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKC4pKSwNCiAgZml0LndpdGhfc2hhcGUlJCUNCiAgICBjYmluZChtb2RlbD0iV2l0aCBzaGFwZSBhcyBjb3ZhcmlhdGUiLA0KICAgICAgICAgIGVxdWF0aW9uPXBhc3RlMCgiU2VlIFN1cHBsZW1lbnRhcnkgSW5mb3JtYXRpb24iKSwNCiAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKC4pKSwNCiAgZml0LmZ1c2lmb3JtJSQlDQogICAgY2JpbmQobW9kZWw9IkZ1c2lmb3JtIHNwZWNpZXMgb25seSIsDQogICAgICAgICAgZXF1YXRpb249cGFzdGUwKCJMbihUTCkgPSAiLHJvdW5kKC4kY29lZmZpY2llbnRzWzJdLDQpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAiICogTG4oT09MKSAiLGlmZWxzZSguJGNvZWZmaWNpZW50c1sxXT4xLCIrIiwiLSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAiICIsYWJzKHJvdW5kKC4kY29lZmZpY2llbnRzWzFdLDQpKSksDQogICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyguKSksDQogIGZpdC5yZWxhdGl2ZWRlcHRoNCUkJQ0KICAgIGNiaW5kKG1vZGVsPSJJbmNsdWRpbmcgYm9keSBkZXB0aCBhcyBjb3ZhcmlhdGUiLA0KICAgICAgICAgIGVxdWF0aW9uPXBhc3RlMCgiU2VlIFN1cHBsZW1lbnRhcnkgSW5mb3JtYXRpb24iKSwNCiAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKC4pKSwNCiAgd2l0aF9zbm91dF9sZW5ndGglJCUNCiAgICBjYmluZChtb2RlbD0iSW5jbHVkaW5nIHNub3V0IGxlbmd0aCBhcyBjb3ZhcmlhdGUiLA0KICAgICAgICAgIGVxdWF0aW9uPXBhc3RlMCgiTG4oVEwpID0gIixyb3VuZCguJGNvZWZmaWNpZW50c1szXSw0KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIiAqIExuKE9PTCkgIixpZmVsc2UoLiRjb2VmZmljaWVudHNbMl0+MSwiKyIsIi0iKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIiAiLGFicyhyb3VuZCguJGNvZWZmaWNpZW50c1syXSw0KSksIiAqIExuKFNOTCkgIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKC4kY29lZmZpY2llbnRzWzFdPjEsIisiLCItIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICIgIixhYnMocm91bmQoLiRjb2VmZmljaWVudHNbMV0sNCkpKSwNCiAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKC4pKSwNCiAgZml0LnBlbGFnaWMlJCUNCiAgICBjYmluZChtb2RlbD0iUGVsYWdpYyBzcGVjaWVzIG9ubHkiLA0KICAgICAgICAgIGVxdWF0aW9uPXBhc3RlMCgiTG4oVEwpID0gIixyb3VuZCguJGNvZWZmaWNpZW50c1syXSw0KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIiAqIExuKE9PTCkgIixpZmVsc2UoLiRjb2VmZmljaWVudHNbMV0+MSwiKyIsIi0iKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIiAiLGFicyhyb3VuZCguJGNvZWZmaWNpZW50c1sxXSw0KSkpLA0KICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoLikpLA0KICBmaXQubm9fYWNhbnRob3B0ZXJ5Z2lpMiUkJQ0KICAgIGNiaW5kKG1vZGVsPSJFeGNsdWRpbmcgQWNhbnRob3B0ZXJ5Z2lpLCBmdXNpZm9ybSBhbmQgZWxvbmdhdGUgdGF4YSBvbmx5IiwNCiAgICAgICAgICBlcXVhdGlvbj1wYXN0ZTAoIkxuKFRMKSA9ICIscm91bmQoLiRjb2VmZmljaWVudHNbMl0sNCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICIgKiBMbihPT0wpICIsaWZlbHNlKC4kY29lZmZpY2llbnRzWzFdPjEsIisiLCItIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICIgIixhYnMocm91bmQoLiRjb2VmZmljaWVudHNbMV0sNCkpKSwNCiAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKC4pKSwNCiAgZml0LnNoYXJrcyUkJQ0KICAgIGNiaW5kKG1vZGVsPSJTaGFya3Mgb25seSIsDQogICAgICAgIGVxdWF0aW9uPXBhc3RlMCgiTG4oVEwpID0gIixyb3VuZCguJGNvZWZmaWNpZW50c1syXSw0KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICIgKiBMbihPT0wpICIsaWZlbHNlKC4kY29lZmZpY2llbnRzWzFdPjEsIisiLCItIiksDQogICAgICAgICAgICAgICAgICAgICAgICAiICIsYWJzKHJvdW5kKC4kY29lZmZpY2llbnRzWzFdLDQpKSksDQogICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoLikpLA0KICBmaXQuc2hhcGVfY2xhZGUzJSQlDQogICAgY2JpbmQobW9kZWw9IldpdGggc2hhcGUsIGFsbG93aW5nIHZhcmlhYmxlIHNsb3BlIGZvciBDaG9uZHJpY2h0aHllcyIsDQogICAgICAgICAgZXF1YXRpb249cGFzdGUwKCJTZWUgU3VwcGxlbWVudGFyeSBJbmZvcm1hdGlvbiIpLA0KICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoLikpLA0KICBsbShsb2codG90YWxfbGVuZ3RoKX5sb2coaGVhZF9sZW5ndGgpLGRhdGFfZmluYWwpJSQlDQogICAgY2JpbmQobW9kZWw9IkhlYWQgbGVuZ3RoIiwNCiAgICAgICAgICBlcXVhdGlvbj1wYXN0ZTAoIkxuKFRMKSA9ICIscm91bmQoLiRjb2VmZmljaWVudHNbMl0sNCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICIgKiBMbihIREwpICIsaWZlbHNlKC4kY29lZmZpY2llbnRzWzFdPjEsIisiLCItIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICIgIixhYnMocm91bmQoLiRjb2VmZmljaWVudHNbMV0sNCkpKSwNCiAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKC4pKQ0KICApJT4lDQogIHJvd25hbWVzX3RvX2NvbHVtbigpJT4lDQogIHNlbGVjdChtb2RlbCxOLGVxdWF0aW9uLGFkanIyLEFJQyxCSUMsUEUsQ0YsYWRqUEUsU0VFKSU+JQ0KICByZW5hbWUoRXF1YXRpb249ZXF1YXRpb24sDQogICAgICAgICAiJVBFIj1QRSwNCiAgICAgICAgICJyMmFkaiI9YWRqcjIsDQogICAgICAgICAiJVBFY2YiPWFkalBFLA0KICAgICAgICAgIiVTRUUiPVNFRSklPiUNCiAgY29sdW1uX3RvX3Jvd25hbWVzKCJtb2RlbCIpDQogICklPiUNCiAgd3JpdGUuY3N2KCJEZXZvbmlhbiBGaXNoIFRhbGUgVGFibGUgMSAoTW9kZWwgU3RhdGlzdGljcykuY3N2IikNCmBgYA0KDQoNCk92ZXJhbGwsIHRoZSBtb2RlbHMgdGhhdCBwcm9kdWNlIHRoZSBiZXN0IHN1cHBvcnQgc3RhdGlzdGljcyAoQUlDLCAlUEU8c3ViPmNmPC9zdWI+KSBhcmUgZWl0aGVyIHRoZSBtb2RlbCBjb25zaWRlcmluZyBvbmx5IGZ1c2lmb3JtIGZpc2hlcyAoYGZpdC5mdXNpZm9ybWApLCB0aGUgbW9kZWwgdGhhdCBpbmNsdWRlcyBhIGNydWRlIGFjY291bnRpbmcgZm9yIHNoYXBlIGJ5IGluY2x1ZGluZyBgc2hhcGVgIGFzIGEgY2F0ZWdvcmljYWwgdmFyaWFibGUgKGBmaXQud2l0aF9zaGFwZWApLCBvciB0aGUgbW9kZWwgdGhhdCBpbmNsdWRlcyBzaGFwZSBhbmQgYWxsb3dzIHNsb3BlIHRvIGRpZmZlcmVudGlhdGUgYmV0d2VlbiBjaG9uZHJpY2h0aHlhbnMgYW5kIGFsbCBvdGhlciBmaXNoZXMgZHVlIHRvIHRoZSBsYXJnZXN0IGNob25kcmljdGh5YW5zIHByaW1hcmlseSBiZWluZyBzaG9ydC1ib2RpZWQgbGFtbmlkcyBhbmQgdGhlIHNtYWxsZXN0IGJlaW5nIGVsb25nYXRlLWJvZGllZCBjYXJwZXQgc2hhcmtzIChgZml0LnNoYXBlX2NsYWRlM2ApLg0KDQojIFByZWRpY3RpbmcgYm9keSBtYXNzIG9mICpEdW5rbGVvc3RldXMqIGFuZCBvdGhlciBhcnRocm9kaXJlcw0KDQojIyBFc3RpbWF0aW5nIHByZWNhdWRhbCBhbmQgZm9yayBsZW5ndGggaW4gKkR1bmtsZW9zdGV1cyoNCg0KIyMjIEVzdGltYXRpbmcgZm9yayBsZW5ndGggdXNpbmcgdG90YWwgbGVuZ3RoIG9mIGFsbCBvYnNlcnZhdGlvbnMNCg0KYGBge3J9DQp0b3RhbF9sZW5ndGhfdG9fZm9ya19sZW5ndGg8LWRhdGFfZmluYWwlPiUNCiAgZmlsdGVyKGxlbmd0aF9hcz09InRvdGFsIGxlbmd0aCIpJT4lDQogIGZpbHRlcighKHRvdGFsX2xlbmd0aD09Zm9ya19sZW5ndGgpKSU+JQ0KICBmaWx0ZXIoIWlzLm5hKGZvcmtfbGVuZ3RoKSklPiUNCiAgbG0obG9nKGZvcmtfbGVuZ3RoKX5sb2codG90YWxfbGVuZ3RoKSsoY2xhZGU9PSJQbGFjb2Rlcm1pInxjbGFkZT09IkNob25kcmljaHRoeWVzIikraGFiaXRhdCwuKQ0Kc3VtbWFyeSh0b3RhbF9sZW5ndGhfdG9fZm9ya19sZW5ndGgpDQpgYGANCg0KIyMjIEVzdGltYXRpbmcgcHJlY2F1ZGFsIGxlbmd0aCB1c2luZyB0b3RhbCBsZW5ndGggb2YgYWxsIG9ic2VydmF0aW9ucw0KDQpgYGB7cn0NCmRhdGFfZmluYWwlPiUNCiAgZmlsdGVyKGxlbmd0aF9hcz09InRvdGFsIGxlbmd0aCIpJT4lDQogIG11dGF0ZShjbGFkZT1yZWxldmVsKGZhY3RvcihjbGFkZSksIlBsYWNvZGVybWkiKSklPiUNCiAgZmlsdGVyKCFpcy5uYShwcmVjYXVkYWxfbGVuZ3RoKSklJCUNCiAgbG0obG9nKHByZWNhdWRhbF9sZW5ndGgpfmxvZyh0b3RhbF9sZW5ndGgpK2NsYWRlK2hhYml0YXQsLiklPiUNCiAgc3VtbWFyeSgpDQpgYGANCg0KQmFzZWQgb24gdGhpcywgc2V0dGluZyBgUGxhY29kZXJtaWAgYXMgdGhlIGJhc2UgbGV2ZWwgZm9yIGBjbGFkZWAgYW5kIHRoZSBsaW1pdGVkIGV2aWRlbmNlIHdlIGhhdmUgZm9yIHByZWNhdWRhbC90b3RhbCBsZW5ndGhzIGluIGFydGhyb2RpcmVzLCBjaG9uZHJpY2h0aHlhbnMgc2VlbSBsaWtlIGEgc3VpdGFibGUgbW9kZWwgdG8gY2FsY3VsYXRlIGZvcmsgYW5kIHByZWNhdWRhbCBsZW5ndGggaW4gYXJ0aHJvZGlyZXMsIGJ1dCBhY3Rpbm9wdGVyeWdpYW5zIHNob3cgYSBjb21wYXJhdGl2ZWx5IHNob3J0ZXIgY2F1ZGFsIGZpbiBhbmQgbG9uZ2VyIHByZWNhdWRhbCByZWdpb24gdGhhbiBhcnRocm9kaXJlcy4NCg0KYGBge3J9DQp0b3RhbF9sZW5ndGhfdG9fc3RhbmRhcmRfbGVuZ3RoPC1kYXRhX2ZpbmFsJT4lDQogIGZpbHRlcihsZW5ndGhfYXM9PSJ0b3RhbCBsZW5ndGgiKSU+JQ0KICBmaWx0ZXIoIWlzLm5hKHByZWNhdWRhbF9sZW5ndGgpKSUkJQ0KICBsbShsb2cocHJlY2F1ZGFsX2xlbmd0aCl+bG9nKHRvdGFsX2xlbmd0aCkrKGNsYWRlPT0iUGxhY29kZXJtaSJ8Y2xhZGU9PSJDaG9uZHJpY2h0aHllcyIpK2hhYml0YXQrc2hhcGUsLikNCg0Kc3VtbWFyeSh0b3RhbF9sZW5ndGhfdG9fc3RhbmRhcmRfbGVuZ3RoKQ0KYGBgDQoNCiMjIyBQcmVkaWN0aW5nIHRvdGFsIGxlbmd0aCBpbiBwbGFjb2Rlcm1zIGluIHRoaXMgc3R1ZHkgYXNzdW1pbmcgZnVzaWZvcm0gYm9keSBzaGFwZSBhbmQgbW9kZWwgd2l0aCB2YXJpYWJsZSBzbG9wZSBmb3IgY2xhZGUNCg0KYGBge3J9DQpmb3NzaWxfdGF4YTwtZm9zc2lsX3RheGElPiUNCiAgbXV0YXRlKHNoYXBlPSJmdXNpZm9ybSIpJT4lDQogIG11dGF0ZSh0b3RhbF9sZW5ndGg9aWZlbHNlKGlzLm5hKHRvdGFsX2xlbmd0aCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV4cChwcmVkaWN0KGZpdC5zaGFwZV9jbGFkZTMsLikpKg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKGZpdC5zaGFwZV9jbGFkZTMpJENGLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b3RhbF9sZW5ndGgpKSU+JQ0KICBtdXRhdGUocHJlY2F1ZGFsX2xlbmd0aD1pZmVsc2UoaXMubmEocHJlY2F1ZGFsX2xlbmd0aCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBleHAocHJlZGljdCh0b3RhbF9sZW5ndGhfdG9fc3RhbmRhcmRfbGVuZ3RoLC4pKSoNCiAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyh0b3RhbF9sZW5ndGhfdG9fc3RhbmRhcmRfbGVuZ3RoKSRDRiwNCiAgICAgICAgICAgcHJlY2F1ZGFsX2xlbmd0aCksDQogICAgICAgICBmb3JrX2xlbmd0aD1pZmVsc2UoaXMubmEoZm9ya19sZW5ndGgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV4cChwcmVkaWN0KHRvdGFsX2xlbmd0aF90b19mb3JrX2xlbmd0aCwuKSkqDQogICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHModG90YWxfbGVuZ3RoX3RvX2ZvcmtfbGVuZ3RoKSRDRiwNCiAgICAgICAgICAgZm9ya19sZW5ndGgpKQ0KYGBgDQoNCiMjIEVzdGltYXRpbmcgYm9keSBtYXNzIGluICpEdW5rbGVvc3RldXMqIHVzaW5nIGEgbW9kaWZpZWQgZWxsaXBzb2lkIGVxdWF0aW9uDQoNClRoZSBsZW5ndGgtd2VpZ2h0IGVxdWF0aW9uIGluIHRoaXMgc3R1ZHkgZXhwYW5kcyBvbiB0aGUgbWV0aG9kb2xvZ3kgb2YgQXVsdCBhbmQgTHVvIChbMjAxM10oI3JlZmVyZW5jZXMpKS4gQXVsdCBhbmQgTHVvIChbMjAxM10oI3JlZmVyZW5jZXMpKSBub3RlZCB0aGF0IHRoZSBib2R5IG9mIHRoZSBmaXNoIGlzIGJlc3QgbW9kZWxlZCBhcyBhbiBlbGxpcHNvaWQsIHdpdGggdGhlIGVxdWF0aW9uOg0KDQokJA0Kd2VpZ2h0ID0gZ2lydGheMipsZW5ndGggKyBnaXJ0aCpsZW5ndGggKyBnaXJ0aF4yICsgZ2lydGggKyBsZW5ndGgNCiQkDQoNCmxlYXZpbmcgZml2ZSBjb2VmZmljaWVudHMgdG8gYmUgc29sdmVkIGZvciBleHBlcmltZW50YWxseS4NCg0KVG8gdGhpcyBJIGFkZCB0aHJlZSBvdGhlciBwYXJhbWV0ZXJzLiBBbHRob3VnaCBhIGZpc2gncyBib2R5IGlzIGJlc3QgbW9kZWxlZCBhcyBhbiBlbGxpcHNvaWQsIGl0IGlzIGFuIGFzeW1tZXRyaWNhbCBlbGxpcHNvaWQsIHdpdGggb25lIGJ1bGJvdXMgaGFsZiAoZW5jb21wYXNzaW5nIHRoZSBoZWFkIHJlZ2lvbikgYW5kIG9uZSB0aGlubmVyLCBtb3JlIHRhcGVyaW5nIGhhbGYgKGluY2x1ZGluZyBwb3N0ZXJpb3IgaGFsZiBvZiB0aGUgYm9keSB0YXBlcmluZyB0byB0aGUgY2F1ZGFsIHBlZHVuY2xlKS4gVGhlc2UgbXVzdCBiZSBhY2NvdW50ZWQgZm9yIHNlcGFyYXRlbHkgdG8gYWNjdXJhdGVseSBlc3RpbWF0ZSBib2R5IG1hc3MsIGVzcGVjaWFsbHkgaW4gYW4gaW50ZXJzcGVjaWZpYyBtb2RlbCwgYXMgdGhlIHByb3BvcnRpb24gb2YgdGhlIHRoaWNrZXIgaGVhZCByZWdpb24gdG8gdGhlIHRoaW5uZXIgYWJkb21lbiBhbmQgdHJ1bmsgdmFyaWVzIGFtb25nIGZpc2hlcy4gRS5nLiwgKkR1bmtsZW9zdGV1cyogaGFzIGFuIGV4dHJlbWVseSB0aGljayBoZWFkIGFuZCBsaWtlbHkgaGFkIGEgc3Ryb25nbHkgdGFwZXJpbmcgY2F1ZGFsIHJlZ2lvbiBnaXZlbiBpdHMgcGVsYWdpYyBoYWJpdHMgKFtDYXJyLCAyMDEwXSgjcmVmZXJlbmNlcykpLCBhbmQgY2Fubm90IGJlIG1vZGVsZWQgYXMgYSBzaW1wbGUgZWxsaXBzb2lkLg0KDQpBZGRpdGlvbmFsbHksIHRoZSBjYXVkYWwgZmluIGNvbnRyaWJ1dGVzIGEgc21hbGwgYnV0IG5vbi1pbnNpZ25pZmljYW50IGFtb3VudCB0byBib2R5IG1hc3MuIFRoaXMgaXMgZXhwZWN0ZWQgdG8gYmUgZ3JlYXRlciBpbiB0YXhhIHdpdGggaW50ZXJuYWxseSBoZXRlcm9jZXJjYWwgdGFpbHMgdGhhbiB0aG9zZSBpbiB3aGljaCB0aGUgdGFpbCBpcyBzdXBwb3J0ZWQgb25seSBieSBsZXBpZG90cmljaGlhLiBBcyBhIHJlc3VsdCwgdGhlIG1vZGVsIHdhcyBmaXQgd2l0aCBhbiBhZGRpdGlvbmFsIHBhcmFtZXRlciBmb3IgY2F1ZGFsIGZpbnMsIHdpdGggYSB2YXJpYWJsZSBsZXZlbCBmb3IgdGF4YSB3aXRoIGludGVybmFsbHkgaGV0ZXJvY2VyY2FsIHRhaWxzIChDaG9uZHJpY2h0aHllcywgUGxhY29kZXJtaSwgU2FyY29wdGVyeWdpaSwgYW5kIGJhc2FsIGFjdGlub3B0ZXJ5Z2lhbiBsaW5lYWdlcyBsaWtlIFBvbHlwdGVyaWZvcm1lcywgQW1paWZvcm1lcywgTGVwaXNvc3RlaWZvcm1lcywgYW5kIEFjaXBlbnNlcmlmb3JtZXMpIHdoZXJlIHRoZSBzcGluYWwgY29yZCBleHRlbmRzIGludG8gdGhlIGNhdWRhbCBmaW4sIGFuZCB0aG9zZSB3aXRoIGludGVybmFsbHkgaG9tb2NlcmNhbCB0YWlscyAoVGVsZW9zdGVpKSB3aGVyZSBpdCBpcyB0cnVuY2F0ZWQuDQoNClRheGEgdGhhdCBsYWNrZWQgYSBkaXN0aW5jdCBjYXVkYWwgZmluIGFuZCBwcmUtY2F1ZGFsIHJlZ2lvbiAoQW5ndWlsbGlmb3JtZXMsICpNb2xhKikgaGFkIHRvIGJlIGV4Y2x1ZGVkIGZyb20gdGhlIG1vZGVsLCBhcyB0aGVpciBsYWNrIG9mIGEgY2F1ZGFsIGZpbiByZXN1bHRlZCBpbiBhIHNpZ25pZmljYW50IGJpYXMgaW4gYm9keSBtYXNzIGVzdGltYXRlcyB3aGVuIGFwcGxpZWQgdG8gZm9zc2lsIHRheGEuDQoNCkFub3RoZXIgcG90ZW50aWFsIHNvdXJjZSBvZiBlcnJvciBpbiBib2R5IG1hc3MgZXN0aW1hdGVzIG9mIGZpc2hlcyBpcyB0aGUgc3dpbSBibGFkZGVyLiBCZWNhdXNlIHRoZSBzd2ltIGJsYWRkZXIgaXMgYSBsYXJnZSwgZ2FzLWZpbGxlZCBvcmdhbiwgdGhpcyByZWR1Y2VzIHRoZSB0b3RhbCBkZW5zaXR5IG9mIHRoZSBmaXNoLCBhbmQgaXMgZXhwZWN0ZWQgdG8gbWFrZSBmaXNoIHdpdGggc3dpbSBibGFkZGVycyBjb21wYXJhdGl2ZWx5IGxpZ2h0ZXIgYXQgdGhlIHNhbWUgdm9sdW1lIHRoYW4gdGhvc2Ugd2l0aG91dCAoW0FsZXhhbmRlciwgMTk2N10oI3JlZmVyZW5jZXMpKS4gRmlzaCB3aXRob3V0IHN3aW0gYmxhZGRlcnMgaW5jbHVkZSBQZXRyb215em9udGlmb3JtZXMgYW5kIENob25kcmljaHRoeWFucywgYnV0IGFsc28gbW9zdCBzY29tYnJpZHMgKGJ1dCBub3QgaXN0aW9waG9yaWZvcm1zOyBzZWUgTWNDdW5lIGFuZCBDYXJsc29uIDIwMDQgZm9yIGEgbW9yZSBjb21wcmVoZW5zaXZlIGxpc3Qgb2Ygc3dpbSBibGFkZGVyIGxvc3MgaW4gZmlzaGVzKS4gV2hldGhlciBhcnRocm9kaXJlcyBoYWQgYSBzd2ltIGJsYWRkZXIgaXMgdW5rbm93bjogdGhlIGZyZXF1ZW50bHkgY2l0ZWQgcHJlc2VuY2Ugb2YgbHVuZ3MgaW4gdGhlIGFudGlhcmNoICpCb3RocmlvbGVwaXMqIGFwcGVhcnMgdG8gYmUgYSBwYWxlb250b2xvZ2ljYWwgbWlzaW50ZXJwcmV0YXRpb24gKHNlZSBbR291amV0IDIwMTFdKCNyZWZlcmVuY2VzKSBhbmQgW1RyaW5hanN0aWMgZXQgYWwuIDIwMjJdKCNyZWZlcmVuY2VzKSkgYnV0IHRoZXJlIGlzIGNpcmN1bXN0YW50aWFsIGV2aWRlbmNlIGJhc2VkIG9uIHRhcGhvbm9teSBhbmQgYm9keSBzaGFwZSB0aGF0ICpEdW5rbGVvc3RldXMqIGRpZCBub3QgaGF2ZSBhbiBlbmxhcmdlZCBsaXZlciBzaW1pbGFyIHRvIGV4dGFudCBzaGFya3MgYW5kIGluc3RlYWQgcG9zc2Vzc2VkIHNvbWUgZm9ybSBvZiBnYXMtZmlsbGVkIGZsb2F0YXRpb24gb3JnYW5zIChbQ2FyciAyMDEwXSgjcmVmZXJlbmNlcyk7IEVuZ2VsbWFuIHBlcnMgb2JzLikuIEZvciB0aGUgcHVycG9zZXMgb2YgdGhpcyBzdHVkeSBib3RoIHdlcmUgcmVwb3J0ZWQgaGVyZSwgYnV0IHRoZSBtb3JlIGNvbnNlcnZhdGl2ZSBpbnRlcnByZXRhdGlvbiBvZiAqRHVua2xlb3N0ZXVzKiB3aXRob3V0IGEgc3dpbWJsYWRkZXIgd2FzIGNvbnNpZGVyZWQgdGhlIGNsb3NlciB0byB0aGUgYWN0dWFsIHZhbHVlcy4NCg0KVGhlIGxpc3Qgb2YgYWN0aW5vcHRlcnlnaWFuIHRheGEgY29uc2lkZXJlZCBhcyBsYWNraW5nIHRoZSBzd2ltIGJsYWRkZXIgd2FzIHRha2VuIGZyb20gTWNDdW5lIGFuZCBDYXJsc29uIChbMjAwNF0oI3JlZmVyZW5jZXMpKSwgQ29sbGV0dGUgYW5kIE5hdWVuIChbMTk4M10oI3JlZmVyZW5jZXMpKSwgYW5kIEJvbmUgKFsxOTcyXSgjcmVmZXJlbmNlcykpLiAgQWxsIGV4dGFudCBub24tb3N0ZWljaHRoeWFuIHRheGEgd2VyZSBjb25zaWRlcmVkIGFzIGxhY2tpbmcgdGhlIHN3aW0gYmxhZGRlci4gVGhlIERpcG5vaSB3ZXJlIGNvZGVkIGFzIGhhdmluZyBzd2ltIGJsYWRkZXJzIGFuZCB0aGUgTGF0aW1lcmlpZGFlIGNvZGVkIGFzIGFic2VudDsgaW4gY29lbG9jYW50aHMgdGhlIGFuY2VzdHJhbCBzd2ltLWJsYWRkZXIgaGFzIGJlZW4gY29udmVydGVkIGludG8gYSBmYXQtZmlsbGVkIG9yZ2FuIChbQ3VwZWxsbyBldCBhbC4gMjAxNV0oKCNyZWZlcmVuY2VzKSkpLiBObyBhdHRlbXB0IHdhcyBtYWRlIHRvIGFjY291bnQgZm9yIHZhcmlhdGlvbiBpbiB0aGUgc2l6ZSBvZiB0aGUgc3dpbSBibGFkZGVyIGJldHdlZW4gZmlzaGVzLg0KDQpHaXJ0aCB3YXMgbm90IGF2YWlsYWJsZSBmb3IgYWxsIGZpc2hlcywgYnV0IGBib2R5X3dpZHRoYCBhbmQgYGJvZHlfZGVwdGhgIHdlcmUuIFRoZXJlIGlzIG5vIGVhc3kgd2F5IHRvIGNhbGN1bGF0ZSB0aGUgcGVyaW1ldGVyIG9mIGFuIGVsbGlwc2UsIGJ1dCBvbmUgY29tbW9ubHkgdXNlZCB3YXkgdG8gZG8gc28gaXMgUmFtYW51amFuJ3MgYXBwcm94aW1hdGlvbiAoW1JhbWFudWphbiAxOTYyXSgjcmVmZXJlbmNlcyk7IFtWaWxsYXJpbm8gMjAwOF0oI3JlZmVyZW5jZXMpKSwgd2hpY2ggaXMgZGVmaW5lZCBhcy4uLg0KDQokJA0KcGVyaW1ldGVyID0gz4AgKiBbKGErYikrXGZyYWN7MyooYS1iKV4yKX17MTAoYStiKStcc3FydHthXjIrMTRhYitiXjJ9fV0NCiQkDQoNCkluIHdoaWNoICRhJCBpcyB0aGUgcmFkaXVzIG9mIHRoZSBtYWpvciAobG9uZ2VzdCkgYXhpcyBvZiB0aGUgZWxsaXBzZSBhbmQgJGIkIGlzIHRoZSByYWRpdXMgb2YgdGhlIG1pbm9yIChpLmUuLCB0aGUgYXhpcyBwZXJwZW5kaWN1bGFyIHRvIHRoZSBtYWpvciBheGlzKS4gSW4gYSBmaXNoLCB0aGUgbWFqb3IgYXhpcyBvZiB0aGUgYm9keSBpbiBmcm9udGFsIHZpZXcgaXMgYWxtb3N0IGFsd2F5cyB0aGUgZG9yc292ZW50cmFsIGF4aXMsIHdpdGggdGhlIG1lZGlvbGF0ZXJhbCBheGlzIGJlaW5nIG11Y2ggbmFycm93ZXIgKGVzcGVjaWFsbHkgaW4gYWN0aW5vcHRlcnlnaWFucykuDQoNClRodXMsIGEgdXNlci1kZWZpbmVkIGZvcm11bGEgd2FzIHdyaXR0ZW4gaW4gUiB0byBjYWxjdWxhdGUgZ2lydGggYmFzZWQgb24gUmFtYW51amFuJ3MgYXBwcm94aW1hdGlvbiwgYXNzdW1pbmcgJFxmcmFje1x0ZXh0e2JvZHlfZGVwdGh9fXsyfSQgcmVwcmVzZW50ZWQgdGhlIHJhZGl1cyBvZiB0aGUgbWFqb3IgYXhpcyBhbmQgJFxmcmFje1x0ZXh0e2JvZHlfd2lkdGh9fXsyfSQgcmVwcmVzZW50ZWQgdGhlIHJhZGl1cyBvZiB0aGUgbWlub3IuDQoNCmBgYHtyLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KI1Rlc3RpbmcgaWYgYXBwcm94aW1hdGlvbiBmb3JtdWxhIHdvcmtzDQpmb3NzaWxfdGF4YSU+JQ0KICBtdXRhdGUoZ2lydGgyPXJhbWFudWphbi5hcHByb3goYm9keV9kZXB0aCxib2R5X3dpZHRoKSklPiUNCiAgZHJvcF9uYShnaXJ0aCxnaXJ0aDIpJT4lDQogIHNlbGVjdCh0YXhvbixzcGVjaW1lbixnaXJ0aCxnaXJ0aDIpJT4lDQogIHJlbW92ZV9yb3duYW1lcygpJT4lDQogIGthYmxlKGRpZ2l0cz0xLGNvbC5uYW1lcz1jKCJUYXhvbiIsIlNwZWNpbWVuIiwiTWVhc3VyZWQgR2lydGgiLCJFc3RpbWF0ZWQgR2lydGggVXNpbmcgUmFtYW51amFuIEFwcHJveGltYXRpb24iKSwNCiAgICAgICAgY2FwdGlvbj0iVGVzdGluZyBSYW1hbnVqYW4ncyBmb3JtdWxhIGZvciBhcHByb3hpbWF0aW5nIHRoZSBwZXJpbWV0ZXIgb2YgYW4gZWxsaXBzZSBvbiBhcnRocm9kaXJlIHNwZWNpbWVucyBmb3Igd2hpY2ggZ2lydGggY291bGQgYmUgZGlyZWN0bHkgbWVhc3VyZWQuIEFsbCBtZWFzdXJlbWVudHMgaW4gY20uIiklPiUNCiAgY29sdW1uX3NwZWMoMSwgaXRhbGljID0gVCklPiUNCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KVGhlIGFjdHVhbCB2YWx1ZXMgb2YgYGdpcnRoYCBhcmUgc2xpZ2h0bHkgaGlnaGVyIHRoYW4gdGhlIGFwcHJveGltYXRpb24uIFRoaXMgaW1wbGllcyB0aGF0IGFydGhyb2RpcmVzIGFyZSBzbGlnaHRseSAiYmxvY2tpZXIiIHRoYW4gd291bGQgYmUgZXhwZWN0ZWQgaWYgbW9kZWxlZCBieSBhIHBlcmZlY3QgZWxsaXBzZS4gSG93ZXZlciwgdGhpcyBzaG91bGQgcHJvdmUgc3VmZmljaWVudCBmb3IgZXN0aW1hdGluZyBnaXJ0aCBpbiBmaXNoIHNwZWNpbWVucyBmb3Igd2hpY2ggYGJvZHlfZGVwdGhgIGFuZCBgYm9keV93aWR0aGAgaXMgYXZhaWxhYmxlIGJ1dCBgZ2lydGhgIHdhcyBub3QgcmVwb3J0ZWQgb3IgbWVhc3VyYWJsZSwgbW9zdCBvZiB3aGljaCBhcmUgbXVjaCBsZXNzIGJsb2NreSB0aGFuIGFydGhyb2RpcmVzLg0KDQpgYGB7cixjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30NCmZpdC5lbGxpcHNvaWQ8LQ0KICBkYXRhX2ZpbmFsJT4lDQogICAgICAgZmlsdGVyKGxlbmd0aF9hcz09InRvdGFsIGxlbmd0aCIsDQogICAgICAgICAgICAgIHRvdGFsX2xlbmd0aCE9cHJlY2F1ZGFsX2xlbmd0aCklPiUNCiAgICAgICBkcm9wX25hKHByZWNhdWRhbF9sZW5ndGgpJT4lDQogICAgICAgbXV0YXRlKGdpcnRoPWlmZWxzZSghaXMubmEoZ2lydGgpLGdpcnRoLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgcmFtYW51amFuLmFwcHJveChib2R5X2RlcHRoLGJvZHlfd2lkdGgpKSklJCUNCiAgbG0obG9nKGJvZHlfbWFzcyl+SShsb2coZ2lydGgpXjIpKmxvZyhwcmVjYXVkYWxfbGVuZ3RoKStzd2ltYmxhZGRlcisNCiAgICAgICBsb2coZ2lydGgpKmxvZyhwcmVjYXVkYWxfbGVuZ3RoKSsNCiAgICAgICBsb2cocHJlY2F1ZGFsX2xlbmd0aCkqbG9nKGhlYWRfbGVuZ3RoKS1sb2coaGVhZF9sZW5ndGgpKw0KICAgICAgIGhldGVyb2NlcmNhbDpsb2codG90YWxfbGVuZ3RoLXByZWNhdWRhbF9sZW5ndGgpLA0KICAgICAuKQ0KZml0LmVsbGlwc29pZF9ub19zd2ltYmxhZGRlcjwtDQogIGRhdGFfZmluYWwlPiUNCiAgICAgICBmaWx0ZXIobGVuZ3RoX2FzPT0idG90YWwgbGVuZ3RoIiwNCiAgICAgICAgICAgICAgdG90YWxfbGVuZ3RoIT1wcmVjYXVkYWxfbGVuZ3RoKSU+JQ0KICAgICAgIGRyb3BfbmEocHJlY2F1ZGFsX2xlbmd0aCklPiUNCiAgICAgICBtdXRhdGUoZ2lydGg9aWZlbHNlKCFpcy5uYShnaXJ0aCksZ2lydGgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICByYW1hbnVqYW4uYXBwcm94KGJvZHlfZGVwdGgsYm9keV93aWR0aCkpKSUkJQ0KICBsbShsb2coYm9keV9tYXNzKX5JKGxvZyhnaXJ0aCleMikqbG9nKHByZWNhdWRhbF9sZW5ndGgpKw0KICAgICAgIGxvZyhnaXJ0aCkqbG9nKHByZWNhdWRhbF9sZW5ndGgpKw0KICAgICAgIGxvZyhwcmVjYXVkYWxfbGVuZ3RoKSpsb2coaGVhZF9sZW5ndGgpLWxvZyhoZWFkX2xlbmd0aCkrDQogICAgICAgaGV0ZXJvY2VyY2FsOmxvZyh0b3RhbF9sZW5ndGgtcHJlY2F1ZGFsX2xlbmd0aCksDQogICAgIC4pDQpgYGANCg0KYGBge3J9DQpzdW1tYXJ5KGZpdC5lbGxpcHNvaWQpDQpyZWdyZXNzaW9uLnN0YXRzKGZpdC5lbGxpcHNvaWQpDQpgYGANCg0KVGhlIGVsbGlwc29pZCBtb2RlbCBwcmVkaWN0cyBib2R5IG1hc3MgdmVyeSB3ZWxsLCBjb25zaWRlcmluZyBpdCBpcyBhbiBpbnRlcnNwZWNpZmljIG1vZGVsIGJhc2VkIG9uIGFsbCBmaXNoZXMgYW5kIG1hbnkgb2YgdGhlIGRhdGEgd2VyZSBlc3RpbWF0ZWQgYmFzZWQgb24gc3BlY2llcy1zcGVjaWZpYyBsZW5ndGgtd2VpZ2h0IGVxdWF0aW9ucy4gQWRkaXRpb25hbGx5LCB0aGUgdHdvIGV4dHJhIHBhcmFtZXRlcnMgYWRkZWQgKHJhdGlvIG9mIGhlYWQgbGVuZ3RoIHRvIHByZWNhdWRhbCBsZW5ndGgsIGNhdWRhbCBmaW4gc2l6ZSwgYW5kIHByZXNlbmNlIG9mIHN3aW0gYmxhZGRlcikgZXhwbGFpbmVkIHN0YXRpc3RpY2FsbHkgZGV0ZWN0YWJsZSBhbW91bnRzIG9mIHZhcmlhdGlvbiBpbiB0aGUgbW9kZWwuIFRoZXJlIGFyZSBwcm9iYWJseSBtb3JlIHByZWNpc2Ugd2F5cyB0byBtb2RlbCB0aGUgYm9keSBtYXNzIG9mICpEdW5rbGVvc3RldXMqIChlLmcuLCBhIHZvbHVtZXRyaWMgbW9kZWw7IFtCcmFzc2V5IDIwMTZdKCNyZWZlcmVuY2VzKSksIGJ1dCBmb3IgdGhlIHB1cnBvc2VzIG9mIHRoaXMgc3R1ZHkgdGhpcyBzZWVtcyByZWxpYWJsZSBlbm91Z2guDQoNCmBgYHtyLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KcmJpbmQoDQogIG1vZGVsMT0obG0obG9nKGJvZHlfbWFzcyl+SShsb2coZ2lydGgpXjIpKmxvZyhwcmVjYXVkYWxfbGVuZ3RoKSsNCiAgICAgICBsb2coZ2lydGgpKmxvZyhwcmVjYXVkYWxfbGVuZ3RoKSsNCiAgICAgICBsb2cocHJlY2F1ZGFsX2xlbmd0aCkqbG9nKGhlYWRfbGVuZ3RoKS1sb2coaGVhZF9sZW5ndGgpKw0KICAgICAgIChjbGFkZSE9IkFjdGlub3B0ZXJ5Z2lpInxoaWdoZXJfZ3JvdXA9PSJCYXNhbCBBY3Rpbm9wdGVyeWdpaSIpOmxvZyh0b3RhbF9sZW5ndGgtcHJlY2F1ZGFsX2xlbmd0aCksDQogICAgIGRhdGFfZmluYWwlPiVkcm9wX25hKGdpcnRoLGJvZHlfZGVwdGgsYm9keV93aWR0aCklPiUNCiAgICAgICBtdXRhdGUocHJlY2F1ZGFsX2xlbmd0aD1pZmVsc2UoaXMubmEocHJlY2F1ZGFsX2xlbmd0aCl8DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG90YWxfbGVuZ3RoPT1wcmVjYXVkYWxfbGVuZ3RoLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b3RhbF9sZW5ndGgtMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJlY2F1ZGFsX2xlbmd0aCkpKSklPiUNCiAgICByZWdyZXNzaW9uLnN0YXRzKC4pLA0KICBtb2RlbDI9KGxtKGxvZyhib2R5X21hc3MpfkkobG9nKGdpcnRoKV4yKSpsb2cocHJlY2F1ZGFsX2xlbmd0aCkrDQogICAgICAgbG9nKGdpcnRoKSpsb2cocHJlY2F1ZGFsX2xlbmd0aCkrDQogICAgICAgbG9nKHByZWNhdWRhbF9sZW5ndGgpKmxvZyhoZWFkX2xlbmd0aCktbG9nKGhlYWRfbGVuZ3RoKSsNCiAgICAgICAoY2xhZGUhPSJBY3Rpbm9wdGVyeWdpaSJ8aGlnaGVyX2dyb3VwPT0iQmFzYWwgQWN0aW5vcHRlcnlnaWkiKTpsb2codG90YWxfbGVuZ3RoLXByZWNhdWRhbF9sZW5ndGgpLA0KICAgICBkYXRhX2ZpbmFsJT4lZHJvcF9uYShnaXJ0aCxib2R5X2RlcHRoLGJvZHlfd2lkdGgpJT4lDQogICAgICAgbXV0YXRlKHByZWNhdWRhbF9sZW5ndGg9aWZlbHNlKGlzLm5hKHByZWNhdWRhbF9sZW5ndGgpfA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX2xlbmd0aD09cHJlY2F1ZGFsX2xlbmd0aCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG90YWxfbGVuZ3RoLTEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZWNhdWRhbF9sZW5ndGgpLA0KICAgICAgICAgICAgICBnaXJ0aD1yYW1hbnVqYW4uYXBwcm94KGJvZHlfZGVwdGgsYm9keV93aWR0aCkpKSklPiUNCiAgICByZWdyZXNzaW9uLnN0YXRzKC4pKSU+JQ0KICBgcm93bmFtZXM8LWAoYygiTW9kZWwgVXNpbmcgRGlyZWN0bHkgTWVhc3VyZWQgR2lydGggKHdoZW4gYXZhaWxhYmxlKSIsDQogICAgICAgICAgICAgICAgIk1vZGVsIFVzaW5nIEdpcnRoIEVzdGltYXRlZCBmcm9tIERlcHRoIGFuZCBXaWR0aCIpKQ0KYGBgDQoNCkEgbW9kZWwgdXNpbmcgdGhlIGRpcmVjdGx5LW1lYXN1cmVkIHZhbHVlIGZvciBnaXJ0aCwgd2hlbiBpdCB3YXMgZGlyZWN0bHkgbWVhc3VyZWQgaW4gdGhlIHNwZWNpbWVucyBpbiBxdWVzdGlvbiAodXN1YWxseSBpbiB0aGUgY29sbGVjdGlvbnMgb2YgdGhlIENNTkgsIEZTQkMsIGFuZCBPU1VNKSBwcm9kdWNlZCBoaWdoZXIgZXJyb3JzIGFuZCB3b3JzZSBBSUMgYW5kIEJJQyB2YWx1ZXMgd2hlbiBhbGwgb3RoZXIgdmFyaWFibGVzIGFyZSBoZWxkIGVxdWFsLiBUaGlzIGlzIGxpa2VseSB0byBiZSBkdWUgdG8gbWVhc3VyZW1lbnQgZXJyb3IgaW4gbWVhc3VyaW5nIGdpcnRoIHJhdGhlciB0aGFuIG1vZGVsbGluZyBpdCBiYXNlZCBvbiBgYm9keV93aWR0aGAgYW5kIGBib2R5X2RlcHRoYC4NCg0KSG93ZXZlciwgbWVhc3VyZWQgZ2lydGggc2hvdWxkIHN0aWxsIGJlIGFjY3VyYXRlIGZvciBhcnRocm9kaXJlcyBiZWNhdXNlIHRoZSBpc3N1ZXMgd2l0aCBgZ2lydGhgIHdlcmUgcHJpbWFyaWx5IGRyaXZlbiBieSB0aGUgZGlmZmljdWx0aWVzIG9mIG1lYXN1cmluZyBgZ2lydGhgIGluIGZsdWlkLXByZXNlcnZlZCBmaXNoLCByYXRoZXIgdGhhbiBmb3NzaWxpemVkIHNwZWNpbWVucy4NCg0KYGBge3IsY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZSd9DQpmb3NzaWxfdGF4YTwtZm9zc2lsX3RheGElPiUNCiAgbXV0YXRlKGdpcnRoMz1naXJ0aCwNCiAgICAgICAgIGdpcnRoPWlmZWxzZShpcy5uYShnaXJ0aCkscmFtYW51amFuLmFwcHJveChib2R5X2RlcHRoLGJvZHlfd2lkdGgpLGdpcnRoKSklPiUNCiAgYXVnbWVudChmaXQuZWxsaXBzb2lkLA0KICAgICAgICAgIG5ld2RhdGE9LiwNCiAgICAgICAgICBpbnRlcnZhbD0icHJlZGljdCIpJT4lDQogIG11dGF0ZShnaXJ0aD1naXJ0aDMpJT4lDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5lbGxpcHNvaWQpJENGKSklPiUNCiAgbXV0YXRlKGJvZHlfbWFzcz0uZml0dGVkLA0KICAgICAgICAgbWFzczEubG93ZXI9Lmxvd2VyLA0KICAgICAgICAgbWFzczEudXBwZXI9LnVwcGVyKQ0KDQpkdW5rX3dlaWdodHM8LWZvc3NpbF90YXhhJT4lDQogIG11dGF0ZShzd2ltYmxhZGRlcj1ULA0KICAgICAgICAgZ2lydGg9aWZlbHNlKGlzLm5hKGdpcnRoKSxyYW1hbnVqYW4uYXBwcm94KGJvZHlfZGVwdGgsYm9keV93aWR0aCksZ2lydGgpKSU+JQ0KICBhdWdtZW50KGZpdC5lbGxpcHNvaWQsDQogICAgICAgICAgbmV3ZGF0YT0uLA0KICAgICAgICAgIGludGVydmFsPSJwcmVkaWN0IiklPiUNCiAgbXV0YXRlKHN3aW1ibGFkZGVyPUYpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgibWFzczIiLC4pKSklPiUNCiAgbXV0YXRlKGFjcm9zcyhtYXNzMi5maXR0ZWQ6bWFzczIudXBwZXIsfmV4cCguKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5lbGxpcHNvaWQpJENGKSklPiUNCiAgYXVnbWVudChmaXQuZWxsaXBzb2lkX25vX3N3aW1ibGFkZGVyLA0KICAgICAgICAgIG5ld2RhdGE9LiwNCiAgICAgICAgICBpbnRlcnZhbD0icHJlZGljdCIpJT4lDQogIG11dGF0ZShnaXJ0aD1naXJ0aDMpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgibWFzczMiLC4pKSklPiUNCiAgbXV0YXRlKGFjcm9zcyhtYXNzMy5maXR0ZWQ6bWFzczMudXBwZXIsfmV4cCguKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5lbGxpcHNvaWRfbm9fc3dpbWJsYWRkZXIpJENGKSklPiUNCiAgZHJvcF9uYShib2R5X21hc3MpJT4lDQogIGFycmFuZ2UoYm9keV9tYXNzKSU+JQ0KICBtdXRhdGUoYWNyb3NzKGMoYm9keV9tYXNzLG1hc3MxLmxvd2VyLG1hc3MxLnVwcGVyLG1hc3MyLmZpdHRlZCxtYXNzMi5sb3dlcixtYXNzMi51cHBlciwNCiAgICAgICAgICAgICAgICAgIG1hc3MzLmxvd2VyLG1hc3MzLnVwcGVyLG1hc3MzLmZpdHRlZCksfi4vMTAwMCksDQogICAgICAgICBtYXNzMS5yYW5nZT1wYXN0ZTAoIigiLHJvdW5kKG1hc3MxLmxvd2VyLDMpLCLigJMiLHJvdW5kKG1hc3MxLnVwcGVyLDMpLCIpIiksDQogICAgICAgICBtYXNzMi5yYW5nZT1wYXN0ZTAoIigiLHJvdW5kKG1hc3MyLmxvd2VyLDMpLCLigJMiLHJvdW5kKG1hc3MyLnVwcGVyLDMpLCIpIiksDQogICAgICAgICBtYXNzMy5yYW5nZT1wYXN0ZTAoIigiLHJvdW5kKG1hc3MzLmxvd2VyLDMpLCLigJMiLHJvdW5kKG1hc3MzLnVwcGVyLDMpLCIpIikpJT4lDQogIHNlbGVjdCh0YXhvbixzcGVjaW1lbix0b3RhbF9sZW5ndGgsbWFzczMuZml0dGVkLG1hc3MzLnJhbmdlLGJvZHlfbWFzcyxtYXNzMS5yYW5nZSxtYXNzMi5maXR0ZWQsbWFzczIucmFuZ2UpDQpkdW5rX3dlaWdodHMlPiUNCiAga2FibGUoZGlnaXRzPWMoMSwxLDEsMywzLDMsMywzLDMpLGFsaWduPWMoImwiLCJjIiwiYyIsImMiLCJjIiwiYyIsImMiLCJjIiwiYyIpLA0KICAgICAgICBjb2wubmFtZXM9YygiVGF4b24iLCJTcGVjaW1lbiIsIlRvdGFsIExlbmd0aCIsDQogICAgICAgICAgICAgICAgICAgICJFc3QuIiwiOTUlIFAuSS4iLA0KICAgICAgICAgICAgICAgICAgICAiRXN0LiIsIjk1JSBQLkkuIiwNCiAgICAgICAgICAgICAgICAgICAgIkVzdC4iLCI5NSUgUC5JLiIpLA0KICAgICAgICBjYXB0aW9uPSJCb2R5IG1hc3NlcyBvZiBhcnRocm9kaXJlcyBlc3RpbWF0ZWQgdXNpbmcgYSBtdWx0aXZhcmlhdGUgbW9kZWwuIEJvZHkgbWFzc2VzIHJlcG9ydGVkIGluIGtnLCBidXQgd2l0aCB0aHJlZSBkaWdpdHMgaW4gb3JkZXIgdG8gZGlzcGxheSBlc3RpbWF0ZWQgbWFzc2VzIGZvciBzbWFsbGVyIGFydGhyb2RpcmUgdGF4YS4iKSU+JQ0KICBhZGRfaGVhZGVyX2Fib3ZlKGMoIiAiPTMsIk9taXR0aW5nIHN3aW1ibGFkZGVyIGFzIGV4cGxhbmF0b3J5IHZhcmlhYmxlIj0yLCJBc3N1bWluZyBubyBzd2ltIGJsYWRkZXIiPTIsIkFzc3VtaW5nIHByZXNlbmNlIG9mIHN3aW0gYmxhZGRlciI9MikpJT4lDQogIGNvbHVtbl9zcGVjKDEsaXRhbGljPVQpJT4lDQogIGthYmxlX3N0eWxpbmcoKQ0KYGBgDQoNClVudXN1YWxseSwgdGhlIHByZXNlbmNlIG9mIGEgc3dpbSBibGFkZGVyIHNlZW1zIHRvIHByZWRpY3QgKmhpZ2hlciogYm9keSBtYXNzZXMgaW4gZmlzaGVzLCBjb250cmFyeSB0byB0aGVvcmV0aWNhbCBleHBlY3RhdGlvbnMgKFtBbGV4YW5kZXIgMTk2N10oI3JlZmVyZW5jZXMpKS4gT25lIHBvc3NpYmlsaXR5IGlzIHRoaXMgbWlnaHQgYmUgZHVlIHRvIGEgbGFyZ2UsIG9pbC1maWxsZWQgbGl2ZXIgaW4gc2hhcmtzLCB3aGljaCBvY2N1cGllcyBtb3N0IG9mIHRoZSBndXQgY2F2aXR5IGFuZCBjYW4gcmVwcmVzZW50IDIwLTI1JSBvZiB0b3RhbCBib2R5IG1hc3MgaW4gbWFueSBwZWxhZ2ljIHNwZWNpZXMgWyhDb3JuZXIgZXQgYWwuIDE5NjksIEthdWZmbWFuIDE5NTAsIERlIE1hZGRhbGVuYSBldCBhbC4gMjAwM10oI3JlZmVyZW5jZXMpKS4gU2hhcmsgbGl2ZXIgaXMgbXVjaCBkZW5zZXIgdGhhbiBhIHN3aW0gYmxhZGRlciAoMC45IHZlcnN1cyAwLjAwMTIyIGcvY21eM147IFtCYWxkcmlkZ2UgMTk3MF0oI3JlZmVyZW5jZXMpKSwgYnV0IGJlY2F1c2UgaXQgb2NjdXBpZXMgYSBwcm9wb3J0aW9uYWxseSBncmVhdGVyIHZvbHVtZSBvZiB0aGUgYm9keSBjYXZpdHkgaXQgcmVzdWx0cyBpbiB0aGUgZGVuc2l0eSBvZiB0aGUgZW50aXJlIGFuaW1hbCBiZWluZyBsb3dlci4gVGhpcyBpcyBzaW1pbGFyIHRvIGhvdyBpbiBiaXJkcyBhbmQgc2F1cm9wb2QgZGlub3NhdXJzIHdob2xlLWJvZHkgZGVuc2l0eSBpcyBvbmx5IGFib3V0IDAuNyBnL2NtXjNeIGR1ZSB0byB0aGUgcHJlc2VuY2Ugb2YgZXh0ZW5zaXZlIGFpciBzYWNzIGFuZCBwbmV1bWF0aWNpdHkgKFtXZWRlbCAyMDA5XSgjcmVmZXJlbmNlcykpLCBkZXNwaXRlIHRoZSByZXN0IG9mIHRoZSBhbmltYWwncyB0aXNzdWVzIGJlaW5nIGNvbXBhcmFibHkgaW4gZGVuc2l0eSB0byBvdGhlciB0ZXRyYXBvZHMuDQoNCkl0IGlzIG5vdCBjbGVhciBpZiBhcnRocm9kaXJlcyBzaG91bGQgYmUgdHJlYXRlZCBhcyBoYXZpbmcgYSBsaXZlciBjb21wYXJhYmxlIGluIHNpemUgdG8gc2hhcmtzLiBQcmVzZXJ2ZWQgbGl2ZXIgcmVtYWlucyBoYXZlIGJlZW4gZG9jdW1lbnRlZCBmb3IgKkluY2lzb3NjdXR1bSByaXRjaGVpKiAoc2VlIFtUcmluYWpzdGljIGV0IGFsLiAyMDIyXSgjcmVmZXJlbmNlcykpLCBidXQgdGhlc2Ugc3BlY2ltZW5zIHN1Z2dlc3QgYSBsaXZlciBmYXIgc21hbGxlciB0aGFuIHNlZW4gaW4gZXh0YW50IHNoYXJrcyAoRW5nZWxtYW4sIHVucHVibGlzaGVkIGRhdGEgaW4gcHJlcCkuIEJlY2F1c2Ugb2YgdGhpcyB1bmNlcnRhaW50eSAoYW5kIHRvIGxpbWl0IHRoZSBzY29wZSBvZiB0aGUgcHJlc2VudCBjb250cmlidXRpb24pLCB0aGUgYXV0aG9yIHByZWZlcnMgdG8gbGVhdmUgdGhlIHF1ZXN0aW9uIG9mIGxpdmVyIHNpemUgYWxvbmUgZm9yIG5vdyBhbmQgbWVyZWx5IGNvbnNpZGVyIHRoZSBwcmVzZW50IG9yIGFic2VuY2Ugb2YgYSBzd2ltIGJsYWRkZXIsIGFzIHRoZSBhYnNlbmNlIG9mIHRoaXMgb3JnYW4gaXMgYmV0dGVyLWVzdGFibGlzaGVkIGluIGFydGhyb2RpcmVzIChbR291amV0IDIwMTEsIFRyaW5hanN0aWMgZXQgYWwuIDIwMjJdKCNyZWZlcmVuY2VzKSkNCg0KIyMjIEVzdGltYXRpbmcgbWFzcyB1c2luZyBvbmx5IGxhcmdlLCBwZWxhZ2ljIGZpc2hlcw0KDQpCZWNhdXNlICpEdW5rbGVvc3RldXMqIGFuZCAqSGVpbnR6aWNodGh5cyogYXJlIHNvIHNpbWlsYXIgaW4gbGVuZ3RoLXdlaWdodCByZWxhdGlvbnNoaXAgdG8gbGFtbmlkIHNoYXJrcyBhbmQgdHVuYXMsIGl0IGlzIHdvcnRoIGNvbnNpZGVyaW5nIGhvdyBhbiBlcXVhdGlvbiB1c2luZyBvbmx5IGxhcmdlLCBwZWxhZ2ljIGZpc2hlcyBlc3RpbWF0ZXMgYm9keSBtYXNzIGluIHRoZXNlIHRheGEuIFRoaXMgZXF1YXRpb24gY29uc2lkZXJzIHRocmVlIG1haW4gZ3JvdXBzOiBTY29tYnJpZGFlLCBMYW1uaWZvcm1lcywgYW5kIElzdGlvcGhvcmlmb3JtZXMuIEZvciB0aGlzIG1vZGVsIGZvcmsgbGVuZ3RoIHdhcyB1c2VkIGluc3RlYWQgb2YgcHJlY2F1ZGFsIGxlbmd0aCwgYXMgZm9yayBsZW5ndGggaXMgdGhlIHR5cGljYWwgdW5pdCBvZiBtZWFzdXJlbWVudCBpbiB0aGVzZSB0aHJlZSBmaXNoIGdyb3VwcyBhbmQgb2Z0ZW4gcmVxdWlyZXMgbm8gZXN0aW1hdGlvbiAoY29udHJhIHByZWNhdWRhbC9zdGFuZGFyZCBsZW5ndGgsIHdoaWNoIGZvciB0dW5hcyBvZnRlbiBlbmNvbXBhc3NlcyBtb3N0IG9mIHRoZSBjYXVkYWwgZmluIGR1ZSB0byB0aGUgbW9ycGhvbG9neSBvZiB0aGUgcGVkdW5jbGUpLiBObyBjb21wZW5zYXRpb24gd2FzIG1hZGUgZm9yIGNhdWRhbCBmaW4gc3RydWN0dXJlIGluIHRoaXMgbW9kZWwgZHVlIHRvIHVzaW5nIGZvcmsgbGVuZ3RoLg0KDQoqKk5vdGU6KiogKk1pdHN1a3VyaW5hIG93c3RvbmkqIHdhcyBleGNsdWRlZCBiZWNhdXNlIGl0IGlzIGEgYmF0aHlkZW1lcnNhbCB0YXhvbiB3aXRoIGEgbG9uZyBjYXVkYWwgZmluIGFuZCBsYWNrcyBmb3JrIGxlbmd0aCwgYW5kIHRyeWluZyB0byBiYWNrLWNhbGN1bGF0ZSBmb3JrIGxlbmd0aCBhbmQgYWRkICpNaXRzdWt1cmluYSogdG8gdGhpcyBhbmFseXNpcyByZXN1bHRzIGluIG1hc3NpdmUgZXJyb3IgZm9yICpNLiBvd3N0b25pKg0KDQpgYGB7cixjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30NCmZpdC5lbGxpcHNvaWRfcGVsYWdpYzwtDQogIGxtKGxvZyhib2R5X21hc3MpfkkobG9nKGdpcnRoKV4yKSpsb2coZm9ya19sZW5ndGgpKw0KICAgICAgIGxvZyhnaXJ0aCkqbG9nKGZvcmtfbGVuZ3RoKStzd2ltYmxhZGRlcisNCiAgICAgICBsb2coZm9ya19sZW5ndGgpKmxvZyhoZWFkX2xlbmd0aCktbG9nKGhlYWRfbGVuZ3RoKSwNCiAgICAgZGF0YV9maW5hbCU+JQ0KICAgICAgIGZpbHRlcihmYW1pbHkgJWluJSBjKCJTY29tYnJpZGFlIil8DQogICAgICAgICAgICAgICAgb3JkZXIgJWluJSBjKCJMYW1uaWZvcm1lcyIsIklzdGlvcGhvcmlmb3JtZXMiKSwNCiAgICAgICAgICAgICAgIWdlbnVzICVpbiUgYygiTWl0c3VrdXJpbmEiKSklPiUNCiAgICAgICBmaWx0ZXIobGVuZ3RoX2FzPT0idG90YWwgbGVuZ3RoIiklPiUNCiAgICAgICBtdXRhdGUoZm9ya19sZW5ndGg9aWZlbHNlKGlzLm5hKGZvcmtfbGVuZ3RoKXwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b3RhbF9sZW5ndGg9PXByZWNhdWRhbF9sZW5ndGgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX2xlbmd0aC0xLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3JrX2xlbmd0aCksDQogICAgICAgICAgICAgIGdpcnRoPWlmZWxzZSghaXMubmEoZ2lydGgpLGdpcnRoLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgcmFtYW51amFuLmFwcHJveChib2R5X2RlcHRoLGJvZHlfd2lkdGgpKSkpDQpzdW1tYXJ5KGZpdC5lbGxpcHNvaWRfcGVsYWdpYykNCnJlZ3Jlc3Npb24uc3RhdHMoZml0LmVsbGlwc29pZF9wZWxhZ2ljKQ0KDQpmb3NzaWxfdGF4YSU+JQ0KICBtdXRhdGUoZ2lydGgyPWdpcnRoLA0KICAgICAgICAgZ2lydGg9aWZlbHNlKCFpcy5uYShnaXJ0aCksZ2lydGgsDQogICAgICAgICAgICAgICAgICAgICAgcmFtYW51amFuLmFwcHJveChib2R5X2RlcHRoLGJvZHlfd2lkdGgpKSklPiUNCiAgYXVnbWVudChmaXQuZWxsaXBzb2lkX3BlbGFnaWMsDQogICAgICAgICAgbmV3ZGF0YT0uLA0KICAgICAgICAgIGludGVydmFsPSJwcmVkaWN0IiklPiUNCiAgbXV0YXRlKGdpcnRoPWdpcnRoMiklPiUNCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0LmVsbGlwc29pZF9wZWxhZ2ljKSRDRikpJT4lDQogIG11dGF0ZShib2R5X21hc3M9LmZpdHRlZCklPiUNCiAgZHJvcF9uYShib2R5X21hc3MpJT4lDQogIGZpbHRlcihnZW51cyAlaW4lIGMoIkR1bmtsZW9zdGV1cyIsIkhlaW50emljaHRoeXMiKSklPiUNCiAgYXJyYW5nZShib2R5X21hc3MpJT4lDQogIG11dGF0ZShhY3Jvc3MoYyhib2R5X21hc3MsLmxvd2VyLC51cHBlciksfi4vMTAwMCkpJT4lDQogIHNlbGVjdCh0YXhvbixzcGVjaW1lbix0b3RhbF9sZW5ndGgsYm9keV9tYXNzLC5sb3dlciwudXBwZXIpJT4lDQogIGthYmxlKGRpZ2l0cz0xLGNvbC5uYW1lcz1jKCJUYXhvbiIsIlNwZWNpbWVuIiwiVG90YWwgTGVuZ3RoIiwiRXN0LiIsIkxvd2VyIDk1JSBQLkkuIiwiVXBwZXIgOTUlIFAuSS4iKSwNCiAgICAgICAgY2FwdGlvbj0iQm9keSBtYXNzZXMgb2YgcGVsYWdpYyBhcnRocm9kaXJlcyBlc3RpbWF0ZWQgdXNpbmcgYSBtdWx0aXZhcmlhdGUgbW9kZWwgYmFzZWQgb24gdGhlIHRocmVlIG1ham9yIGxpdmluZyBncm91cHMgb2YgZnVzaWZvcm0sIHBlbGFnaWMgZmlzaGVzIChMYW1uaWZvcm1lcywgU2NvbWJyaWRhZSwgYW5kIElzdGlvcGhvcmlmb3JtZXMpLiBCb2R5IG1hc3NlcyByZXBvcnRlZCBpbiBrZy4iKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQojIyBMZW5ndGgtd2VpZ2h0IHJlbGF0aW9uc2hpcHMgaW4gYXJ0aHJvZGlyZXMNCg0KIyMjIEFsbCBmaXNoZXMNCg0KKHJlZjpmaXNod2VpZ2h0cykgUGxvdCBvZiBsZW5ndGggdmVyc3VzIHdlaWdodCBhY3Jvc3MgYWxsIGZpc2hlcyB3aXRoIHdlaWdodCBkYXRhIGluIHRoaXMgc3R1ZHksIHNob3dpbmcgaG93IGZpc2hlcyBpbiBnZW5lcmFsIGZvbGxvdyBhIGNvbnNpc3RlbnQgbGVuZ3RoLXdlaWdodCByZWxhdGlvbnNoaXAgKHdpdGggc2lnbmlmaWNhbnQgaW50ZXJzcGVjaWZpYyB2YXJpYXRpb247IEZyb2VzZSAyMDA2KSBhbmQgdGhhdCBwcmVkaWN0ZWQgYm9keSBtYXNzZXMgZm9yIGNvbXBsZXRlIGFydGhyb2RpcmVzIGZhbGwgd2l0aGluIHRoZSByYW5nZSBvZiB2YXJpYXRpb24gc2VlbiBpbiBtb2Rlcm4gZmlzaGVzLiBSZWdyZXNzaW9uIGxpbmUgcmVwcmVzZW50cyB0aGUgc2ltcGxlIGxlbmd0aC13ZWlnaHQgcmVsYXRpb25zaGlwIGZvciBhbGwgZmlzaGVzLCBub3QgdGhlIGJlc3QgZml0IGN1cnZlIGZvciB0aGUgZWxsaXBzb2lkIG1vZGVsLg0KDQpgYGB7cixmaWcuY2FwPSIocmVmOmZpc2h3ZWlnaHRzKSIsY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZSd9DQpnZ3Bsb3QoZGF0YV9maW5hbCU+JQ0KICAgICAgICAgZmlsdGVyKGxlbmd0aF9hcyE9ImVzdGltYXRlZCB0LmwuInxjbGFkZT09IlBsYWNvZGVybWkiLA0KICAgICAgICAgICAgICAgIGlzX2JvZHlfbWFzc19lc3RpbWF0ZWQ9PUZ8IWlzLm5hKGdpcnRoKXwhaXMubmEoYm9keV93aWR0aCkmIWlzLm5hKGJvZHlfZGVwdGgpLA0KICAgICAgICAgICAgICAgICEodG90YWxfbGVuZ3RoPT1wcmVjYXVkYWxfbGVuZ3RoKSksDQogICAgICAgYWVzKHk9Ym9keV9tYXNzLHg9dG90YWxfbGVuZ3RoKSkrDQogIGdlb21fc3RhcihhZXMoc3RhcnNoYXBlPWNsYWRlLGZpbGw9Y2xhZGUpKSsNCiAgZ2VvbV9zbW9vdGgoZm9ybXVsYT15fngsbWV0aG9kPSJsbSIpKw0KICBnZW9tX3N0YXIoZGF0YT1mb3NzaWxfdGF4YSU+JQ0KICAgICAgICAgICAgICBtdXRhdGUoZ2lydGg9aWZlbHNlKGlzLm5hKGdpcnRoKSxyYW1hbnVqYW4uYXBwcm94KGJvZHlfZGVwdGgsYm9keV93aWR0aCksZ2lydGgpKSU+JQ0KICAgICAgICAgICAgICBhdWdtZW50KGZpdC5lbGxpcHNvaWQsDQogICAgICAgICAgICAgICAgICAgICAgbmV3ZGF0YT0uLA0KICAgICAgICAgICAgICAgICAgICAgIGludGVydmFsPSJwcmVkaWN0IiklPiUNCiAgICAgICAgICAgICAgbXV0YXRlKGJvZHlfbWFzcz1leHAoLmZpdHRlZCkqcmVncmVzc2lvbi5zdGF0cyhmaXQuZWxsaXBzb2lkKSRDRiksDQogICAgICAgICAgICBhZXMoc3RhcnNoYXBlPWNsYWRlLGZpbGw9Y2xhZGUpLGNvbG9yPSJ3aGl0ZSIsDQogICAgICAgICAgICBzaXplPTMuNSxzaG93LmxlZ2VuZD1GKSsNCiAgc2NhbGVfc3RhcnNoYXBlX21hbnVhbCh2YWx1ZXM9YygxNSwxMywxMSwxLDI4KSkrDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKGh1ZV9wYWwoKSg0KVsxOjNdLCJibGFjayIsaHVlX3BhbCgpKDQpWzRdKSkrDQogIHNjYWxlX3hfY29udGludW91cyh0cmFucz0nbG9nMTAnKSsNCiAgc2NhbGVfeV9jb250aW51b3VzKHRyYW5zPSJsb2cxMCIsDQogICAgICAgICAgICAgICAgICAgICBicmVha3MgPSB0cmFuc19icmVha3MoImxvZzEwIiwgZnVuY3Rpb24oeCkgMTBeeCxuPTgpLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gdHJhbnNfZm9ybWF0KCJsb2cxMCIsIG1hdGhfZm9ybWF0KDEwXi54KSkpKw0KICBsYWJzKHk9IkJvZHkgTWFzcyAoZykiLHg9IlRvdGFsIExlbmd0aCAoY20pIixmaWxsPSJDbGFkZSIsc3RhcnNoYXBlPSJDbGFkZSIpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249YygwLjgsMC4yKSkNCmBgYA0KDQoqRHVua2xlb3N0ZXVzKiBoYXMgYSBsZW5ndGgtd2VpZ2h0IHJlbGF0aW9uc2hpcCB0aGF0IGlzIG9uIHRoZSBlZGdlIG9mIHRoZSByYW5nZSBvZiB2YXJpYXRpb24gb2YgZmlzaGVzLCBidXQgdGhpcyBpcyB0byBiZSBleHBlY3RlZCBnaXZlbiB0aGUgb3ZlcmFsbCBzaG9ydCwgZGVlcCBib2R5IG9mIHRoaXMgdGF4b24uIEZvciBleGFtcGxlIGNvZWxvY2FudGhzICgqTGF0aW1lcmlhKiBzcHAuKSwgd2hpY2ggYXJlIGFsc28gdmVyeSBkZWVwLWJvZGllZCBhbmQgaGVhdnkgcmVsYXRpdmUgdG8gdGhlaXIgbGVuZ3RoLCBzaG93IGEgc2ltaWxhciBsZW5ndGgtd2VpZ2h0IHJlbGF0aW9uc2hpcC4NCg0KTm90ZSB0aGF0IHRoZSBsb3dlciByZWdyZXNzaW9uIGxpbmUgZm9yIGVsYXNtb2JyYW5jaHMgaXMgbm90IHN0cmljdGx5IGR1ZSB0byBjbGFkZS1zcGVjaWZpYyBkaWZmZXJlbmNlcyBpbiBib2R5IG1hc3MgcmVsYXRpdmUgdG8gdGhlIHBoeXNpY2FsIGRpbWVuc2lvbnMgb2YgdGhlc2UgYW5pbWFscywgYnV0IGR1ZSB0byB0aGUgZmFjdCB0aGF0IGVsYXNtb2JyYW5jaHMgaW4gZ2VuZXJhbCB0ZW5kIHRvIGJlIG11Y2ggbG9uZ2VyIGZvciB0aGVpciB3ZWlnaHQgcmVsYXRpdmUgdG8gb3N0ZWljaHRoeWFucyBhbmQgcGxhY29kZXJtcy4gVGhpcyBjYW4gYmUgc2VlbiBpbiB0aGUgZmFjdCB0aGF0IGVsYXNtb2JyYW5jaCBjaG9uZHJpY2h0aHlhbnMgYW5kICJlbG9uZ2F0ZS1ib2RpZWQiIG9zdGVpY2h0aHlhbnMgcGxvdCBhbG9uZyBzaW1pbGFyIGxpbmVzLCB0aG91Z2ggZWxhc21vYnJhbmNocyB0ZW5kIHRvIHNob3cgbGFyZ2VyIGhlYWRzIHJlbGF0aXZlIHRvIG9zdGVpY2h0aHlhbnMgd2l0aCBlbG9uZ2F0ZSB0cnVua3MgYmVjYXVzZSBhbiBhc3BlY3QgcmF0aW8gdGhhdCB3b3VsZCBiZSBnZW5lcmFsaXplZCBmdXNpZm9ybSBmb3IgZWxhc21vYnJhbmNocyBpcyBlbG9uZ2F0ZSBmb3IgYWN0aW5vcHRlcnlnaWFucyBkdWUgdG8gZGlmZmVyZW5jZXMgaW4gYm9keSBwcm9wb3J0aW9ucyBiZXR3ZWVuIHRoZSBjbGFkZXMgKHNlZSBtYW51c2NyaXB0KS4NCg0KU2ltaWxhcmx5LCBgY29tcHJlc3NpZm9ybWAgZmlzaGVzIHNob3cgaGlnaGVyIGxlbmd0aC13ZWlnaHQgcmVsYXRpb25zaGlwcyB0aGFuIHR5cGljYWwgZmlzaGVzLiBBbGwgb2YgdGhpcyBpcyB0byBiZSBleHBlY3RlZC4gRmlzaGVzIHdpdGggbG9uZywgbmFycm93IGJvZGllcyBhcmUgZ29pbmcgdG8gYmUgbGlnaHRlciBhdCB0aGUgc2FtZSB1bml0IGxlbmd0aCBhcyBhIGZ1c2lmb3JtIGZpc2gsIGFuZCBzaG9ydCwgZGVlcC1ib2RpZWQgZmlzaCBhcmUgZ29pbmcgdG8gYmUgaGVhdmllci4NCg0KT3ZlcmFsbCwgd2hhdCB0aGlzIG1lYW5zIGlzIHRoYXQgaXQgaXMgc2FmZSB0byB1c2UgdGhpcyBtb2RlbCB0byBlc3RpbWF0ZSB0aGUgYm9keSBtYXNzIG9mIGFydGhyb2RpcmVzLCBiZWNhdXNlIHRoZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGVsYXNtb2JyYW5jaHMgYW5kIG9zdGVpY2h0aHlhbnMgaGVyZSBhcmUgZHVlIHRvIHBoeXNpY2FsIHByb3BvcnRpb25zLCB3aGljaCBhcmUgYWNjb3VudGVkIGZvciBieSB0aGUgYGdpcnRoYCBtZWFzdXJlbWVudHMgaW4gdGhlIGVsbGlwc29pZCBtb2RlbCwgcmF0aGVyIHRoYW4gZHVlIHRvIGEgZGlmZmVyZW5jZSB0aGF0IGNhbm5vdCBiZSBtZWFzdXJhYmx5IHF1YW50aWZpZWQgYnkgdGhpcyBkYXRhc2V0Lg0KDQoocmVmOndlaWdodHNoYXBlKSBQbG90IG9mIGxlbmd0aCB2ZXJzdXMgd2VpZ2h0IGluIGZpc2hlcywgc2hvd2luZyB0aGF0IHNoYXJrcyBhbmQgYWN0aW5vcHRlcnlnaWFucyB3aXRoIGVsb25nYXRlIHRydW5rcyAoZWl0aGVyIGVsb25nYXRlIG9yIGFuZ3VpbGxpZm9ybSkgcGxvdCBhbG9uZyBzaW1pbGFyIHJlZ3Jlc3Npb24gbGluZXMuDQoNCmBgYHtyLGZpZy5jYXA9IihyZWY6d2VpZ2h0c2hhcGUpIn0NCmdncGxvdChkYXRhX2ZpbmFsJT4lDQogICAgICAgICBmaWx0ZXIobGVuZ3RoX2FzIT0iZXN0aW1hdGVkIHQubC4ifGNsYWRlPT0iUGxhY29kZXJtaSIsDQogICAgICAgICAgICAgICAgaXNfYm9keV9tYXNzX2VzdGltYXRlZD09RnwhaXMubmEoZ2lydGgpfCFpcy5uYShib2R5X3dpZHRoKSYhaXMubmEoYm9keV9kZXB0aCksDQogICAgICAgICAgICAgICAgISh0b3RhbF9sZW5ndGg9PXByZWNhdWRhbF9sZW5ndGgpKSU+JQ0KICAgICAgICAgbXV0YXRlKGNsYWRlPWlmZWxzZShjbGFkZSAlaW4lIGMoIkFjdGlub3B0ZXJ5Z2lpIiwiU2FyY29wdGVyeWdpaSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiT3N0ZWljaHRoeWVzIixjbGFkZSksDQogICAgICAgICAgICAgICAgc2hhcGUyPWNhc2Vfd2hlbihjbGFkZT09IkNob25kcmljaHRoeWVzIn4iQ2hvbmRyaWNodGh5ZXMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2hhcGU9PSJmdXNpZm9ybSJ+ImZ1c2lmb3JtIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNoYXBlICVpbiUgYygiYW5ndWlsbGlmb3JtIiwiZWxvbmdhdGUiKX4iYW5ndWlsbGlmb3JtL2Vsb25nYXRlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNoYXBlPT0iY29tcHJlc3NpZm9ybSIgfiAiY29tcHJlc3NpZm9ybSIpKSU+JQ0KICAgICAgICAgbXV0YXRlKHNoYXBlMj1mYWN0b3Ioc2hhcGUyLG9yZGVyZWQ9VCxsZXZlbHM9YygiQ2hvbmRyaWNodGh5ZXMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYW5ndWlsbGlmb3JtL2Vsb25nYXRlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImZ1c2lmb3JtIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImNvbXByZXNzaWZvcm0iKSkpJT4lDQogICAgICAgICBmaWx0ZXIoc2hhcGUhPSJmbGF0dGVuZWQiLCEoY2xhZGUgJWluJSBjKCJQZXRyb215em9udGlmb3JtZXMiLCJQbGFjb2Rlcm1pIikpLA0KICAgICAgICAgICAgICAgIHNoYXBlIT0ibWFjcnVyaWZvcm0ifGNsYWRlPT0iQ2hvbmRyaWNodGh5ZXMiKSU+JQ0KICAgICAgICAgZHJvcF9uYShib2R5X21hc3MpLA0KICAgICAgIGFlcyh5PWJvZHlfbWFzcyx4PXRvdGFsX2xlbmd0aCkpKw0KICBnZW9tX3N0YXIoYWVzKHN0YXJzaGFwZT1jbGFkZSxmaWxsPXNoYXBlMikpKw0KICBnZW9tX3N0YXIoZGF0YT0uJT4lZmlsdGVyKGNsYWRlPT0iQ2hvbmRyaWNodGh5ZXMiKSwNCiAgICAgICAgICAgIGFlcyhzdGFyc2hhcGU9Y2xhZGUsZmlsbD1zaGFwZTIpLHNpemU9Mi41LHNob3cubGVnZW5kPUYpKw0KICBnZW9tX3Ntb290aChmb3JtdWxhPXl+eCxtZXRob2Q9ImxtIixhZXMoY29sb3I9c2hhcGUyKSkrDQogIGdlb21fc3RhcihkYXRhPS4lPiVmaWx0ZXIodGF4b249PSJDb2Njb3N0ZXVzIGN1c3BpZGF0dXMiKSwNCiAgICAgICAgICAgIGFlcyhzdGFyc2hhcGU9Y2xhZGUsZmlsbD1zaGFwZTIpKSsNCiAgc2NhbGVfc3RhcnNoYXBlX21hbnVhbCh2YWx1ZXM9YygxMSwxNSksZ3VpZGU9Im5vbmUiKSsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoaHVlX3BhbCgpKDQpKSkrDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YyhodWVfcGFsKCkoNCkpKSsNCiAgc2NhbGVfeF9jb250aW51b3VzKHRyYW5zPSdsb2cxMCcpKw0KICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnM9ImxvZzEwIiwNCiAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IHRyYW5zX2JyZWFrcygibG9nMTAiLCBmdW5jdGlvbih4KSAxMF54LG49OCksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSB0cmFuc19mb3JtYXQoImxvZzEwIiwgbWF0aF9mb3JtYXQoMTBeLngpKSkrDQogIGxhYnMoeT0iQm9keSBNYXNzIChnKSIseD0iVG90YWwgTGVuZ3RoIChjbSkiLGZpbGw9Ikdyb3VwIixzdGFyc2hhcGU9IkNsYWRlIixjb2xvcj0iR3JvdXAiKSsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPWMoMC44LDAuMjUpKSsNCiAgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzdGFyc2hhcGUgPSBjKDExLDE1LDE1LDE1KSkpKQ0KYGBgDQoNCiMjIyBDb21wYXJpbmcgYXJ0aHJvZGlyZXMgdG8gdHVuYXMgYW5kIHNoYXJrcw0KDQpgYGB7cixmaWcuY2FwPSJHcmFwaCBvZiB0b3RhbCBsZW5ndGggdmVyc3VzIGJvZHkgbWFzcyBmb3IgYWxsIHNoYXJrcyBhbmQgdHVuYXMgKFRodW5uaW5pKSBpbiB3aGljaCB3ZWlnaHQgd2FzIGRpcmVjdGx5IHJlY29yZGVkLCBjb21wYXJlZCB0byBlc3RpbWF0ZWQgd2VpZ2h0cyBmb3IgYXJ0aHJvZGlyZXMgaW4gdGhpcyBzdHVkeSIsY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZSd9DQooYXJ0aHJvZGlyZV9sZW5ndGhfd2VpZ2h0PC1nZ3Bsb3QoZGF0YV9maW5hbCU+JQ0KICAgICAgICAgZmlsdGVyKGlzX2JvZHlfbWFzc19lc3RpbWF0ZWQ9PUZ8c3BlY2ltZW49PSJNWkwgMjM5ODEiLCAjTVpMIDIzOTgxIHdhcyBub3QgZHJvcHBlZCBiZWNhdXNlIGFsdGhvdWdoIGl0cyB3ZWlnaHQgd2FzIGVzdGltYXRlZCB0aGlzIHZhbHVlIHdhcyB2ZXJ5IGNsb3NlIHRvIHRoZSByZXBvcnRlZCB0b3RhbCB3ZWlnaHQNCiAgICAgICAgICAgICAgICAhKGJvZHlfbWFzcz09MTk4MDAwMCZnZW51cz09IkNldG9yaGludXMiKSwgI1RoaXMgaW5kaXZpZHVhbCB3YXMgZHJvcHBlZCBiZWNhdXNlIFNwcmluZ2VyIGFuZCBHaWxiZXJ0ICgxOTc2KSBub3RlZCBpdCB0byBiZSB1bnVzdWFsbHkgdW5kZXJ3ZWlnaHQgYW5kIHRoaW4NCiAgICAgICAgICAgICAgICBjbGFkZT09IkNob25kcmljaHRoeWVzInxnZW51cyAlaW4lIGMoIlRodW5udXMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQWxsb3RodW5udXMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQXV4aXMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRXV0aHlubnVzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkthdHN1d29udXMiKSwNCiAgICAgICAgICAgICAgICAhb3JkZXIgJWluJSBjKCJSYWppZm9ybWVzIiwiUmhpbm9wcmlzdGlmb3JtZXMiLCJDaGltYWVyaWZvcm1lcyIpLA0KICAgICAgICAgICAgICAgICFnZW51cyAlaW4lIGMoIkFsb3BpYXMiKSklPiUNCiAgICAgICAgIGJpbmRfcm93cyguLGZvc3NpbF90YXhhJT4lZmlsdGVyKCFpcy5uYShib2R5X3dpZHRoKSxjbGFkZT09IlBsYWNvZGVybWkiKSklPiUNCiAgICAgICAgIGRyb3BfbmEoYm9keV9tYXNzLHRvdGFsX2xlbmd0aCklPiUNCiAgICAgICAgIG11dGF0ZShjbGFkZT1mYWN0b3IoY2FzZV93aGVuKGNsYWRlPT0iQWN0aW5vcHRlcnlnaWkifiJUaHVubmluaSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZhbWlseT09IkxhbW5pZGFlIn4iTGFtbmlkYWUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW51cz09Ik94eW5vdHVzIn4iT3h5bm90dXMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFkZT09IkNob25kcmljaHRoeWVzIn4iT3RoZXIgRWxhc21vYnJhbmNoaWkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFkZT09IlBsYWNvZGVybWkifiJBcnRocm9kaXJhIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9yZGVyZWQ9VCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzPWMoIkFydGhyb2RpcmEiLCJMYW1uaWRhZSIsIk94eW5vdHVzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk90aGVyIEVsYXNtb2JyYW5jaGlpIiwiVGh1bm5pbmkiKSkpJT4lDQogICAgICAgICBhcnJhbmdlKGRlc2MoY2xhZGUpKSwNCiAgICAgICBhZXMoeT1ib2R5X21hc3MseD10b3RhbF9sZW5ndGgpKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKGNvbG9yPWNsYWRlKSxtZXRob2Q9ImxtIixmb3JtdWxhPXl+eCxhbHBoYT0wLjI1KSsNCiAgZ2VvbV9zdGFyKGFlcyhzdGFyc2hhcGU9Y2xhZGUsZmlsbD1jbGFkZSksc2l6ZT0zKSsNCiAgc2NhbGVfc3RhcnNoYXBlX21hbnVhbCh2YWx1ZXM9YygxLDEzLDEzLDEzLDE1KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiQXJ0aHJvZGlyYSIsIkxhbW5pZGFlIiwiKk94eW5vdHVzKiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk90aGVyIENob25kcmljaHRoeWVzIiwiVGh1bm5pbmkiKSkrDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YyhodWVfcGFsKCkoNClbMToyXSwidGFuIixodWVfcGFsKCkoNClbMzo0XSksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiQXJ0aHJvZGlyYSIsIkxhbW5pZGFlIiwiKk94eW5vdHVzKiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiT3RoZXIgQ2hvbmRyaWNodGh5ZXMiLCJUaHVubmluaSIpKSsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoaHVlX3BhbCgpKDQpWzE6Ml0sInRhbiIsaHVlX3BhbCgpKDQpWzM6NF0pLA0KICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiQXJ0aHJvZGlyYSIsIkxhbW5pZGFlIiwiKk94eW5vdHVzKiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJPdGhlciBDaG9uZHJpY2h0aHllcyIsIlRodW5uaW5pIikpKw0KICBnZW9tX3Ntb290aChhZXMoY29sb3I9Y2xhZGUpLG1ldGhvZD0ibG0iLGZvcm11bGE9eX54LHNlPUYsc2hvdy5sZWdlbmQ9RikrDQogIGdlb21fc21vb3RoKGRhdGE9LiU+JWZpbHRlcihjbGFkZT09IkFydGhyb2RpcmEiKSwNCiAgICAgICAgICAgICAgYWVzKGNvbG9yPWNsYWRlKSxtZXRob2Q9ImxtIixmb3JtdWxhPXl+eCxzZT1GLHNob3cubGVnZW5kPUYpKw0KICBnZW9tX2Vycm9yYmFyKGRhdGE9LiU+JWZpbHRlcihjbGFkZT09IkFydGhyb2RpcmEiKSwNCiAgICAgICAgICAgICAgICBhZXMoeW1pbj1tYXNzMS5sb3dlcix5bWF4PW1hc3MxLnVwcGVyKSx3aWR0aD0uMDIpKw0KICBnZW9tX3N0YXIoZGF0YT0uJT4lZmlsdGVyKGNsYWRlPT0iQXJ0aHJvZGlyYSIpLA0KICAgICAgICAgICAgYWVzKHN0YXJzaGFwZT1jbGFkZSxmaWxsPWNsYWRlKSxzaXplPTMuNSxzaG93LmxlZ2VuZCA9IEYpKw0KICBzY2FsZV94X2NvbnRpbnVvdXModHJhbnM9J2xvZzEwJykrDQogIHNjYWxlX3lfY29udGludW91cyh0cmFucz0ibG9nMTAiLA0KICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSB0cmFuc19icmVha3MoImxvZzEwIiwgZnVuY3Rpb24oeCkgMTBeeCxuPTgpLA0KICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSB0cmFuc19mb3JtYXQoImxvZzEwIiwgbWF0aF9mb3JtYXQoMTBeLngpKSkrDQogIGxhYnMoeT0iQm9keSBNYXNzIChnKSIseD0iVG90YWwgTGVuZ3RoIChjbSkiLGZpbGw9IkNsYWRlIixzdGFyc2hhcGU9IkNsYWRlIixjb2xvcj0iQ2xhZGUiKSsNCiAgdGhlbWVfY2xhc3NpYygpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuOCwwLjI1KSwNCiAgICAgICAgbGVnZW5kLnRleHQ9ZWxlbWVudF9tYXJrZG93bigpKSkNCmdnc2F2ZSgiRmlndXJlIDExIChBcnRocm9kaXJlIExlbmd0aC1XZWlnaHRzKS50aWZmIiwNCiAgICAgICBhcnRocm9kaXJlX2xlbmd0aF93ZWlnaHQsZGV2aWNlPSJ0aWZmIixjb21wcmVzc2lvbj0ibHp3IixkcGk9NjAwLHVuaXRzPSJtbSIsd2lkdGg9MTY1LGhlaWdodD0xMTApDQpgYGANCg0KYGBge3IsZmlnLmNhcD0iR3JhcGggb2YgdG90YWwgbGVuZ3RoIHZlcnN1cyBib2R5IG1hc3MgaW4gYXJ0aHJvZGlyZXMsIHNoYXJrcywgYW5kIHR1bmFzIChUaHVubmluaSksIGluY2x1ZGluZyBzcGVjaW1lbnMgd2hvc2UgbWFzcyB3YXMgZXN0aW1hdGVkIHRocm91Z2ggbGVuZ3RoLXdlaWdodCBlcXVhdGlvbnMiLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KZ2dwbG90KGRhdGFfZmluYWwlPiUNCiAgICAgICAgIGZpbHRlcighKGJvZHlfbWFzcz09MTk4MDAwMCZnZW51cz09IkNldG9yaGludXMiKSwgI1RoaXMgaW5kaXZpZHVhbCB3YXMgZHJvcHBlZCBiZWNhdXNlIFNwcmluZ2VyIGFuZCBHaWxiZXJ0ICgxOTc2KSBub3RlZCBpdCB0byBiZSB1bnVzdWFsbHkgdW5kZXJ3ZWlnaHQgYW5kIHRoaW4NCiAgICAgICAgICAgICAgICBjbGFkZT09IkNob25kcmljaHRoeWVzInxnZW51cyAlaW4lIGMoIlRodW5udXMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQWxsb3RodW5udXMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQXV4aXMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRXV0aHlubnVzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkthdHN1d29udXMiKSwNCiAgICAgICAgICAgICAgICAhb3JkZXIgJWluJSBjKCJSYWppZm9ybWVzIiwiUmhpbm9wcmlzdGlmb3JtZXMiLCJDaGltYWVyaWZvcm1lcyIpLA0KICAgICAgICAgICAgICAgICFnZW51cyAlaW4lIGMoIkFsb3BpYXMiKSklPiUNCiAgICAgICAgIGJpbmRfcm93cyguLGZvc3NpbF90YXhhJT4lZmlsdGVyKCFpcy5uYShib2R5X3dpZHRoKSxjbGFkZT09IlBsYWNvZGVybWkiKSklPiUNCiAgICAgICAgIGRyb3BfbmEoYm9keV9tYXNzLHRvdGFsX2xlbmd0aCklPiUNCiAgICAgICAgIG11dGF0ZShjbGFkZT1mYWN0b3IoY2FzZV93aGVuKGNsYWRlPT0iQWN0aW5vcHRlcnlnaWkifiJUaHVubmluaSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZhbWlseT09IkxhbW5pZGFlIn4iTGFtbmlkYWUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW51cz09Ik94eW5vdHVzIn4iT3h5bm90dXMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFkZT09IkNob25kcmljaHRoeWVzIn4iT3RoZXIgRWxhc21vYnJhbmNoaWkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFkZT09IlBsYWNvZGVybWkifiJBcnRocm9kaXJhIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9yZGVyZWQ9VCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzPWMoIkFydGhyb2RpcmEiLCJMYW1uaWRhZSIsIk94eW5vdHVzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk90aGVyIEVsYXNtb2JyYW5jaGlpIiwiVGh1bm5pbmkiKSkpJT4lDQogICAgICAgICBhcnJhbmdlKGRlc2MoY2xhZGUpKSwNCiAgICAgICBhZXMoeT1ib2R5X21hc3MseD10b3RhbF9sZW5ndGgpKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKGNvbG9yPWNsYWRlKSxtZXRob2Q9ImxtIixmb3JtdWxhPXl+eCxhbHBoYT0wLjI1KSsNCiAgZ2VvbV9zdGFyKGFlcyhzdGFyc2hhcGU9Y2xhZGUsZmlsbD1jbGFkZSksc2l6ZT0zKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKGNvbG9yPWNsYWRlKSxtZXRob2Q9ImxtIixmb3JtdWxhPXl+eCxhbHBoYT0wLjI1LHNob3cubGVnZW5kPUYpKw0KICBnZW9tX3Ntb290aChkYXRhPS4lPiVmaWx0ZXIoY2xhZGU9PSJBcnRocm9kaXJhIiksDQogICAgICAgICAgICAgIGFlcyhjb2xvcj1jbGFkZSksbWV0aG9kPSJsbSIsZm9ybXVsYT15fngsc2U9RixzaG93LmxlZ2VuZD1GKSsNCiAgZ2VvbV9lcnJvcmJhcihkYXRhPS4lPiVmaWx0ZXIoY2xhZGU9PSJBcnRocm9kaXJhIiksDQogICAgICAgICAgICAgICAgYWVzKHltaW49bWFzczEubG93ZXIseW1heD1tYXNzMS51cHBlciksd2lkdGg9LjAyKSsNCiAgZ2VvbV9zdGFyKGRhdGE9LiU+JWZpbHRlcihjbGFkZT09IkFydGhyb2RpcmEiKSwNCiAgICAgICAgICAgIGFlcyhzdGFyc2hhcGU9Y2xhZGUsZmlsbD1jbGFkZSksc2l6ZT0zLjUsc2hvdy5sZWdlbmQgPSBGKSsNCiAgc2NhbGVfc3RhcnNoYXBlX21hbnVhbCh2YWx1ZXM9YygxLDEzLDEzLDEzLDE1KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiQXJ0aHJvZGlyYSIsIkxhbW5pZGFlIiwiKk94eW5vdHVzKiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk90aGVyIENob25kcmljaHRoeWVzIiwiVGh1bm5pbmkiKSkrDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YyhodWVfcGFsKCkoNClbMToyXSwidGFuIixodWVfcGFsKCkoNClbMzo0XSksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiQXJ0aHJvZGlyYSIsIkxhbW5pZGFlIiwiKk94eW5vdHVzKiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiT3RoZXIgQ2hvbmRyaWNodGh5ZXMiLCJUaHVubmluaSIpKSsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoaHVlX3BhbCgpKDQpWzE6Ml0sInRhbiIsaHVlX3BhbCgpKDQpWzM6NF0pLA0KICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiQXJ0aHJvZGlyYSIsIkxhbW5pZGFlIiwiKk94eW5vdHVzKiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJPdGhlciBDaG9uZHJpY2h0aHllcyIsIlRodW5uaW5pIikpKw0KICBzY2FsZV94X2NvbnRpbnVvdXModHJhbnM9J2xvZzEwJykrDQogIHNjYWxlX3lfY29udGludW91cyh0cmFucz0ibG9nMTAiLA0KICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSB0cmFuc19icmVha3MoImxvZzEwIiwgZnVuY3Rpb24oeCkgMTBeeCxuPTgpLA0KICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSB0cmFuc19mb3JtYXQoImxvZzEwIiwgbWF0aF9mb3JtYXQoMTBeLngpKSkrDQogIGxhYnMoeT0iQm9keSBNYXNzIChnKSIseD0iVG90YWwgTGVuZ3RoIChjbSkiLGZpbGw9IkNsYWRlIixzdGFyc2hhcGU9IkNsYWRlIixjb2xvcj0iQ2xhZGUiKSsNCiAgdGhlbWVfY2xhc3NpYygpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuOCwwLjIpLA0KICAgICAgICBsZWdlbmQudGV4dD1lbGVtZW50X21hcmtkb3duKCkpDQpgYGANCg0KT3ZlcmFsbCwgdGhpcyBzaG93cyB0aGF0IGFydGhyb2RpcmVzIGV4aGliaXQgYSBsZW5ndGgtd2VpZ2h0IHJlbGF0aW9uc2hpcCBtb3JlIHNpbWlsYXIgdG8gdHVuYXMgdGhhbiBub24tbGFtbmlkIHNoYXJrcywgYW5kIGFyZSB0aHVzIGV4dHJlbWVseSBoZWF2eSBmb3IgdGhlaXIgbGVuZ3RoLiBUaGlzIGluY2x1ZGVzIG5vbi1wZWxhZ2ljIHRheGEgbGlrZSAqSW5jaXNvc2N1dHVtKiBhbmQgKkNvY2Nvc3RldXMqLiBMYXRlciBEZXZvbmlhbiBwZWxhZ2ljIGFydGhyb2RpcmVzICgqSGVpbnR6aWNodGh5cyosICpEdW5rbGVvc3RldXMqKSBzaG93IHZhbHVlcyBtb3JlIHNpbWlsYXIgdG8gdHVuYXMgKFRodW5uaW5pKSBhbmQgbGFtbmlkIHNoYXJrcywgd2hlcmVhcyBnZW9sb2dpY2FsbHkgb2xkZXIgc3BlY2llcyBzaG93IHZhbHVlcyB0aGF0IGFyZSByb3VnaGx5IGludGVybWVkaWF0ZSBiZXR3ZWVuIHRodW5uaW5zL2xhbW5pZHMgYW5kICJ0eXBpY2FsIiBzaGFya3MgbGlrZSBjYXJjaGFyaGluaWRzLCBoZXhhbmNoaWlkcywgZXRjLg0KDQojIyBFc3RpbWF0aW5nIHdlaWdodCBvZiAqRHVua2xlb3N0ZXVzKiBiYXNlZCBvbiBsZW5ndGgtd2VpZ2h0IGVxdWF0aW9uIG9mICpDYXJjaGFyb2RvbiBjYXJjaGFyaWFzKg0KDQpgYGB7cn0NCmR1bmtfd2VpZ2h0c19jYXJjaGFyb2RvbjwtZm9zc2lsX3RheGElPiUNCiAgbGVmdF9qb2luKC4sZHVua2xlb3N0ZXVzX2phd3MlPiVzZWxlY3Qoc3BlY2ltZW4sSk0xOmluZmVyb2duYXRoYWxfbGVuZ3RoKSxieT0ic3BlY2ltZW4iKSU+JQ0KICBmaWx0ZXIoZ2VudXM9PSJEdW5rbGVvc3RldXMiKSU+JQ0KICBtdXRhdGUoSk01PWlmZWxzZShzcGVjaW1lbj09IkNNTkggNTc2OCIsbWVhbigyNi4yLDI5LjMpLEpNNSkpJT4lDQogIG11dGF0ZShKTTU9aWZlbHNlKHNwZWNpbWVuPT0iQ01OSCA2MDkwIixtZWFuKDIwLjM5LDIxLjA5KSxKTTUpKSU+JQ0KICBtdXRhdGUoSk01PWlmZWxzZShzcGVjaW1lbj09IkNNTkggNzA1NCIsbWVhbigyMy43MywyMi44MSksSk01KSklPiUNCiAgbXV0YXRlKHRvdGFsX2xlbmd0aD1leHAocHJlZGljdChmaXQuc2hhcGVfY2xhZGUzLC4pKSoNCiAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQuc2hhcGVfY2xhZGUzKSRDRiklPiUNCiAgbXV0YXRlKHByZWNhdWRhbF9sZW5ndGg9ZXhwKHByZWRpY3QodG90YWxfbGVuZ3RoX3RvX3N0YW5kYXJkX2xlbmd0aCwuKSkqDQogICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHModG90YWxfbGVuZ3RoX3RvX3N0YW5kYXJkX2xlbmd0aCkkQ0YpJT4lDQogIG11dGF0ZSh3ZWlnaHQ9NDUuOTgqKChnaXJ0aC8xMDApXjIqKHByZWNhdWRhbF9sZW5ndGgvMTAwKSleMC45MjY3LA0KICAgICAgICAgYm9keV9kZXB0aF9yYXRpbz1ib2R5X2RlcHRoL09PTCwNCiAgICAgICAgIGphd3JhdGlvPWJvZHlfZGVwdGgvSk01KSU+JQ0KICBmaWx0ZXIoIWlzLm5hKHdlaWdodCkpDQpkdW5rX3dlaWdodHNfY2FyY2hhcm9kb24lPiUNCiAgcm93bmFtZXNfdG9fY29sdW1uKCklPiUNCiAgc2VsZWN0KHNwZWNpbWVuLE9PTCxib2R5X2RlcHRoX3JhdGlvLGphd3JhdGlvLHRvdGFsX2xlbmd0aCxwcmVjYXVkYWxfbGVuZ3RoLGdpcnRoLHdlaWdodCklPiUNCiAgYXJyYW5nZSh3ZWlnaHQpJT4lDQogIGthYmxlKGRpZ2l0cz0xLA0KICAgICAgICBhbGlnbj1jKCJsIiwiYyIsImMiLCJjIiwiYyIsImMiLCJjIiwiYyIpLA0KICAgICAgICBjb2wubmFtZXM9YygiU3BlY2ltZW4iLCJPT0wiLCJCb2R5IERlcHRoL09PTCIsIkJvZHkgRGVwdGgvSk01IiwiRXN0aW1hdGVkIFRvdGFsIExlbmd0aCIsIkVzdGltYXRlZCBQcmVjYXVkYWwgTGVuZ3RoIiwiR2lydGgiLCJFc3RpbWF0ZWQgQm9keSBNYXNzIiksDQogICAgICAgIGNhcHRpb249IkVzdGltYXRlZCBib2R5IG1hc3NlcyBmb3IgPGk+RHVua2xlb3N0ZXVzIHRlcnJlbGxpPC9pPiwgYXNzdW1pbmcgcHJvcG9ydGlvbnMgc2ltaWxhciB0byBhIGdyZWF0IHdoaXRlIHNoYXJrICg8aT5DYXJjaGFyb2RvbiBjYXJjaGFyaWFzPC9pPikgYW5kIHVzaW5nIHRoZSByZWdyZXNzaW9uIG1vZGVsIG9mIE1vbGxldCBhbmQgQ2FpbGxldCAoMTk5NikuIEFsbCBsZW5ndGhzIGluIGNtIGFuZCBhbGwgd2VpZ2h0cyBpbiBrZyIpJT4lDQogIGthYmxlX3N0eWxpbmcoKQ0KYGBgDQoNCiMjIExlbmd0aC13ZWlnaHQgZXF1YXRpb25zIGluICpEdW5rbGVvc3RldXMqDQoNCiMjIyBVc2luZyBtYXNzZXMgZnJvbSBlbGxpcHNvaWQgbW9kZWwNCg0KYGBge3J9DQpkdW5rX2xlbmd0aF93ZWlnaHQ8LWxtKGxvZyhib2R5X21hc3MpfmxvZyh0b3RhbF9sZW5ndGgpLA0KICAgICAgICAgICAgICAgICAgICAgICBmb3NzaWxfdGF4YSAlPiUgZmlsdGVyKGdlbnVzPT0iRHVua2xlb3N0ZXVzIikpDQpzdW1tYXJ5KGR1bmtfbGVuZ3RoX3dlaWdodCkNCmBgYA0KDQojIyMgVXNpbmcgbWFzc2VzIGZyb20gKkNhcmNoYXJvZG9uKiBtb2RlbA0KDQpgYGB7cn0NCnN1bW1hcnkobG0obG9nKHdlaWdodCl+bG9nKHRvdGFsX2xlbmd0aCksZHVua193ZWlnaHRzX2NhcmNoYXJvZG9uKSkNCmBgYA0KDQojIyMgTGVuZ3RoLVdlaWdodCBDdXJ2ZSBmb3IgKkR1bmtsZW9zdGV1cyB0ZXJyZWxsaSoNCg0KKHJlZjpkdW5rd2VpZ2h0Y3VydmUpIExlbmd0aC13ZWlnaHQgY3VydmUgZm9yICpEdW5rbGVvc3RldXMgdGVycmVsbGkqIHVzaW5nIHRoZSBlbGxpcHNvaWQgbW9kZWwsIHdpdGggc2lsaG91ZXR0ZXMgb2YgKkQuIHRlcnJlbGxpKiBhdCBkaWZmZXJlbnQgbGlmZSBzdGFnZXMgc2hvd2luZyBob3cgdGhpcyBvcmdhbmlzbSBiZWNvbWVzIGRlZXBlci1ib2RpZWQgd2l0aCBncm93dGguIFRvdGFsIGxlbmd0aHMgYW5kIGJvZHkgbWFzc2VzIGFyZSBib3RoIGVzdGltYXRlcyBiYXNlZCBvbiB0aGUgcHJlc2VudCBzdHVkeS4NCg0KYGBge3IsZmlnLmNhcD0iKHJlZjpkdW5rd2VpZ2h0Y3VydmUpIn0NCmxtX2Vxbi5hbGwgPC0gZnVuY3Rpb24ocGxvdF9kYXRhKXsNCiAgbSA8LSBkdW5rX2xlbmd0aF93ZWlnaHQ7DQogIGVxIDwtIHN1YnN0aXR1dGUoaXRhbGljKCJib2R5IG1hc3MiKSA9PSBhICUuJSBpdGFsaWMoInRvdGFsIGxlbmd0aCIpXmIqIiwifn5pdGFsaWMocileMn4iPSJ+cjIsIA0KICAgICAgICAgICAgICAgICAgIGxpc3QoYSA9IGZvcm1hdCh1bm5hbWUoZXhwKGNvZWYobSlbMV0pKSwgZGlnaXRzID0gNSksDQogICAgICAgICAgICAgICAgICAgICAgICBiID0gZm9ybWF0KHVubmFtZShjb2VmKG0pWzJdKSwgZGlnaXRzID0gNSksDQogICAgICAgICAgICAgICAgICAgICAgICByMiA9IGZvcm1hdChzdW1tYXJ5KG0pJHIuc3F1YXJlZCwgZGlnaXRzID0gMykpKQ0KICBhcy5jaGFyYWN0ZXIoYXMuZXhwcmVzc2lvbihlcSkpOw0KfQ0KI1Bsb3R0aW5nIGxlbmd0aC13ZWlnaHQgY3VydmUNCmR1bmtfbGVuZ3RoX3dlaWdodF9ncmFwaDwtZ2dwbG90KGZvc3NpbF90YXhhJT4lZmlsdGVyKGdlbnVzPT0iRHVua2xlb3N0ZXVzIiklPiVkcm9wX25hKGJvZHlfbWFzcyksDQogICAgICAgYWVzKHg9dG90YWxfbGVuZ3RoLHk9Ym9keV9tYXNzLzEwMDApKSsNCiAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbj1tYXNzMS5sb3dlci8xMDAwLHltYXg9bWFzczEudXBwZXIvMTAwMCksd2lkdGg9MikrDQogIGdlb21fc21vb3RoKGZvcm11bGE9eX5JKHheZHVua19sZW5ndGhfd2VpZ2h0JGNvZWZmaWNpZW50c1syXSksbWV0aG9kPSJsbSIpKw0KICBnZW9tX3BvaW50KCkrDQogIGxhYnMoeD0iVG90YWwgTGVuZ3RoIChjbSkiLHk9IkJvZHkgTWFzcyAoa2cpIikrDQogIGdlb21fdGV4dChhZXMoeCA9IDE4OCwgeSA9IDIwMDAsIGxhYmVsID0gbG1fZXFuLmFsbChwbG90X2RhdGEpKSxoanVzdD0wLCANCiAgICAgICAgICAgIHBhcnNlID0gVFJVRSxkYXRhLmZyYW1lKCkpKw0KICB0aGVtZV9jb3dwbG90KCkNCg0KKGR1bmtfbGVuZ3RoX3dlaWdodF9ncmFwaDI8LWdnZHJhdygpICsNCiAgZHJhd19pbWFnZSgiRGV2b25pYW4gRmlzaCBUYWxlIFN1cHBsZW1lbnRhcnkgRmlsZSA5IChCaWcgRHVuayBTaWxob3VldHRlKS5wbmciLA0KICAgICAgICAgICAgIHg9MC4zMyx5PTAuMjcsc2NhbGU9MC4xNikgKw0KICBkcmF3X2ltYWdlKCJEZXZvbmlhbiBGaXNoIFRhbGUgU3VwcGxlbWVudGFyeSBGaWxlIDggKE1lZGl1bSBEdW5rIFNpbGhvdWV0dGUpLnBuZyIsDQogICAgICAgICAgICAgeD0tMC4wMTAseT0wLjA2LHNjYWxlPTAuMTQpICsNCiAgZHJhd19pbWFnZSgiRGV2b25pYW4gRmlzaCBUYWxlIFN1cHBsZW1lbnRhcnkgRmlsZSA3IChMaXR0bGUgRHVuayBTaWxob3VldHRlKS5wbmciLA0KICAgICAgICAgICAgIHg9LTAuMyx5PTAuMDIsc2NhbGU9MC4xMikgKw0KICBkcmF3X3Bsb3QoZHVua19sZW5ndGhfd2VpZ2h0X2dyYXBoKSkNCmdnc2F2ZSgiRHVua2xlb3N0ZXVzIExlbmd0aC1XZWlnaHQudGlmZiIsZHVua19sZW5ndGhfd2VpZ2h0X2dyYXBoMiwNCiAgICAgICBkZXZpY2U9InRpZmYiLHdpZHRoPTE2NSxoZWlnaHQ9MTI0LHVuaXRzPSJtbSIsZHBpPTYwMCxjb21wcmVzc2lvbj0ibHp3IikNCmdnc2F2ZSgiRHVua2xlb3N0ZXVzIExlbmd0aC1XZWlnaHQucG5nIixkdW5rX2xlbmd0aF93ZWlnaHRfZ3JhcGgyLA0KICAgICAgIGRldmljZT0icG5nIix3aWR0aD0xNjUsaGVpZ2h0PTEyNCx1bml0cz0ibW0iLGRwaT0zMDApDQpgYGANCg0KSWYgdGhlIHNhbWUgYWxsb21ldHJpYyBlZmZlY3QgdGhhdCBhZmZlY3RzIGFjdGlub3B0ZXJ5Z2lhbnMgYWxzbyBhZmZlY3RzIGFydGhyb2RpcmVzLCB0aGVuIHRoZSBzbWFsbGVyICpEdW5rbGVvc3RldXMqIG1heSBiZSBzbGlnaHRseSBzaG9ydGVyIHRoYW4gcmVjb25zdHJ1Y3RlZCBoZXJlIChieSB+MTAlIGluIENNTkggNzQyND8pLiBIb3dldmVyLCBqdXN0IGxvb2tpbmcgYXQgYXJtb3IgZGltZW5zaW9ucyBhbG9uZSB0aGVyZSBpcyBzdGlsbCBhIGNsZWFyIGRlZXBlbmluZyBlZmZlY3Qgd2l0aCBzaXplLiBOb3RlIHRoYXQgbm9ybWFsbHkgY3JlYXRpbmcgYSBsZW5ndGgtd2VpZ2h0IGN1cnZlIG91dCBvZiBOID0gNCBpcyBub3QgcmVjb21tZW5kZWQsIGFuZCBpcyBvbmx5IGRvbmUgaGVyZSBiZWNhdXNlIG9mIHRoZSBsaW1pdGVkIG51bWJlciBvZiBzcGVjaW1lbnMgb2YgKkQuIHRlcnJlbGxpKiBmb3Igd2hpY2ggM0QgcmVjb25zdHJ1Y3Rpb25zIGFyZSBhdmFpbGFibGUgdGhhdCBib2R5IG1hc3MgY2FuIGJlIGVzdGltYXRlZC4NCg0KVGhlIHJlc3VsdGluZyBhbGxvbWV0cmljIGV4cG9uZW50IHN1Z2dlc3RzIHRoYXQgKkR1bmtsZW9zdGV1cyogYmVjYW1lIGhlYXZpZXIgcmVsYXRpdmUgdG8gaXRzIGxlbmd0aCB0aHJvdWdob3V0IG9udG9nZW55LCB3aGljaCBhZ3JlZXMgd2l0aCB0aGUgb2JzZXJ2YXRpb24gdGhhdCB0aGVyZSBpcyBhIGdlbmVyYWwgZGVlcGVuaW5nIG9mIHRoZSB0aG9yYWNpYyBhcm1vciB0aHJvdWdob3V0IHRoZSBvYnNlcnZlZCBvbnRvZ2VuZXRpYyBzZXJpZXMuIE5vdGU6IGlmIGl0IGlzIGV2ZW50dWFsbHkgZGVtb25zdHJhdGVkIHRoYXQgdGhlIHBvc2l0aXZlIGFsbG9tZXRyeSBpbiBPT0wgY2hhcmFjdGVyaXplcyBzaGFya3MgYW5kIGFsbCBmaXNoZXMgbW9yZSBnZW5lcmFsbHksIHRoZW4gdGhpcyBtaWdodCByZWR1Y2UgdGhlIGFsbG9tZXRyaWMgZXhwb25lbnQgc29tZXdoYXQuIFRoYXQgaXMsIHRoaXMgd291bGQgbWFrZSB0aGUgeW91bmdlciBpbmRpdmlkdWFscyBvZiAqRHVua2xlb3N0ZXVzKiBzaG9ydGVyLCB3aGljaCB0aGVuIHdvdWxkIG1ha2UgdGhlbSBwb3RlbnRpYWxseSBoZWF2aWVyIHJlbGF0aXZlIHRvIHRoZWlyIHJlZHVjZWQgYW50ZXJvcG9zdGVyaW9yIGxlbmd0aCBlc3RpbWF0ZXMgKGFzIGdpcnRoIGlzIHVuY2hhbmdlZCkuIEZyb2VzZSAoMjAwNikgbm90ZXMgdGhhdCBhbGxvbWV0cmljIGV4cG9uZW50cyBpbiBleGNlc3Mgb2YgMy41IGFyZSB2ZXJ5IHVudXN1YWwgZm9yIGZpc2hlcywgYW5kIHRoaXMgc3VnZ2VzdHMgdGhhdCB3aGlsZSAqRHVua2xlb3N0ZXVzKiBkb2VzIGFwcGVhciB0byBnZXQgZGVlcGVyLWJvZGllZCB0aHJvdWdob3V0IG9udG9nZW55IChhcyBpbmRpY2F0ZWQgYnkgaXRzIGRvcnNvdmVudHJhbCB0cnVuayBwcm9wb3J0aW9ucyByZWxhdGl2ZSB0byBtZWFzdXJhYmxlIGFudGVyb3Bvc3RlcmlvciBkaW1lbnNpb25zIGxpa2UgT09MIG9yIHRob3JhY2ljIGFybW9yIGxlbmd0aCksIHRoZSBhbGxvbWV0cmljIGV4cG9uZW50IGVzdGltYXRlZCBoZXJlIG1pZ2h0IHByb3ZlIHRvIGJlIGEgc2xpZ2h0IG92ZXJlc3RpbWF0ZS4NCg0KIyMgQXJtb3Igd2VpZ2h0IG9mIENNTkggNjA5MA0KDQpFbGVtZW50IHwgVm9sdW1lIChjbV4zKSB8DQotLS0tLS0tIHwgLS0tLS0tLS0tLS0tLSB8DQpIZWFkIGFuZCB0aG9yYWNpYyBzaGllbGQgfCAyMTEzMy45Mw0KUmlnaHQgcG9zdGVyb3ZlbnRyb2xhdGVyYWwgcGxhdGUgfCA4ODIuMTANCkxlZnQgcG9zdGVyb3ZlbnRyb2xhdGVyYWwgcGxhdGUgfCA3MTEuNDYNClJpZ2h0IGFudGVyb3ZlbnRyb2xhdGVyYWwgcGxhdGUgfCA2ODUuOTgNCkxlZnQgYW50ZXJvdmVudHJvbGF0ZXJhbCBwbGF0ZSB8IDY3NC41NA0KUG9zdGVyaW9yIG1lZGlhbiBwbGF0ZSB8IDIwOS42MQ0KQW50ZXJpb3IgbWVkaWFuIHBsYXRlIHwgODguOTYNClRvdGFsIHZvbHVtZSB8IDI0Mzg2LjU5DQoNCioqTm90ZToqKiB0aGUgdm9sdW1lIGVzdGltYXRpb24gb2YgdGhlIGhlYWQgYW5kIHRob3JhY2ljIHNoaWVsZCBkb2VzIG5vdCBzdWJ0cmFjdCB0aGUgdm9sdW1lIG9mIHRoZSBtZXRhbCBhcm1hdHVyZSwgd2hpY2ggaXMgbG9jYXRlZCB2ZXJ5IGNsb3NlIHRvIHRoZSBzcGVjaW1lbiBhbmQgY2Fubm90IGJlIGVhc2lseSBzZXBhcmF0ZWQgZnJvbSB0aGUgM0QgbW9kZWwgb2YgdGhlIGZvc3NpbC4gSG93ZXZlciwgdGhlIGFybWF0dXJlIGlzIHNtYWxsIGFuZCB1bm9idHJ1c2l2ZSBlbm91Z2ggdGhhdCB0aGUgZXN0aW1hdGVkIHZvbHVtZSBmb3IgdGhlIGhlYWQgYW5kIHRydW5rIGFybW9yIHNob3VsZCBiZSBjbG9zZSB0byB0aGUgYWN0dWFsIHZhbHVlLCBpZiBzbGlnaHRseSBoaWdoZXIgZHVlIHRvIHRoZSBwcmVzZW5jZSBvZiB0aGUgYXJtYXR1cmUuDQoNCmBgYHtyLGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KZGF0YS5mcmFtZSgiYXJtb3JfbWFzcyI9YygyNDM4Ni41OSoxLjIpLCJkZW5zaXR5Ij0oMS4yMCksInNvdXJjZSI9Indob2xlIGJvbmUsIFdhbGwgZXQgYWwuIDE5ODMiKSU+JQ0KICBhZGRfcm93KCJhcm1vcl9tYXNzIj1jKDI0Mzg2LjU5KjEuMyksImRlbnNpdHkiPSgxLjMwKSwic291cmNlIj0id2hvbGUgYm9uZSwgSUNSUCAxOTk1IiklPiUNCiAgYWRkX3JvdygiYXJtb3JfbWFzcyI9YygyNDM4Ni41OSoxLjIzNiksImRlbnNpdHkiPSgxLjIzNiksInNvdXJjZSI9Indob2xlIGJvbmUsIFNoZXBoYXJkIDE5OTEiKSU+JQ0KYWRkX3JvdygiYXJtb3JfbWFzcyI9YygyNDM4Ni41OSoxLjg2KSwiZGVuc2l0eSI9KDEuODYpLCJzb3VyY2UiPSJBc3N1bWluZyBlbnRpcmUgYm9uZSBpcyBjb3J0aWNhbCBib25lLCBCbGFudG9uIGFuZCBCaWdncyAxOTY4IiklPiUNCiAgbXV0YXRlKGRlbnNpdHk9YXMuY2hhcmFjdGVyKGRlbnNpdHkpKSU+JQ0KICBhZGRfcm93KCJhcm1vcl9tYXNzIj1jKDI0Mzg2LjU5KjAuMjUqMS44NisyNDM4Ni41OSowLjc1KjEuMDYpLCJkZW5zaXR5Ij1jKCIxLjg2IChjb3J0aWNhbCksIDEuMDYgKGNhbmNlbGxvdXMpIiksInNvdXJjZSI9IkFzc3VtaW5nIDI1JSBvZiB0aGUgYm9uZSB2b2x1bWUgaXMgY29ydGljYWwsIGZvbGxvd2luZyBXYWxsIGV0IGFsLiAxOTgzLCBkZW5zaXRpZXMgZnJvbSBCbGFudG9uIGFuZCBCaWdncyAxOTY4IiklPiUNCiAgbXV0YXRlKGFybW9yX21hc3M9YXJtb3JfbWFzcy8xMDAwLA0KICAgICAgICAgcGVyY2VudF9tYXNzPShhcm1vcl9tYXNzL2R1bmtfd2VpZ2h0cyU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihzcGVjaW1lbj09IkNNTkggNjA5MCIpJT4lcHVsbChib2R5X21hc3MpKSoxMDApJT4lIHNlbGVjdChhcm1vcl9tYXNzLGRlbnNpdHkscGVyY2VudF9tYXNzLHNvdXJjZSkgJT4lDQogIGthYmxlKGRpZ2l0cz1jKDIsMywyLDEpLGFsaWduPWMoImMiKSwNCiAgICAgICAgY29sLm5hbWVzID0gYygiTWFzcyBvZiBBcm1vciAoa2cpIiwiRGVuc2l0eSAoZy9jbTMpIiwiQXJtb3IgV2VpZ2h0IGFzIFBlcmNlbnQgQXJtb3ItRnJlZSBCb2R5IE1hc3MiLCJSZWZlcmVuY2VzIiksY2FwdGlvbj0iQXJtb3IgbWFzc2VzIG9mIENNTkggNjA5MCAoZXN0aW1hdGVkIG1hc3Mgd2l0aG91dCBtYWtpbmcgYXNzdW1wdGlvbnMgZm9yIGFybW9yIHVzaW5nIGVsbGlwc29pZCBtb2RlbCIpJT4lDQogIGthYmxlX3N0eWxpbmcoKQ0KYGBgDQoNCiMjIEhvdyBsYXJnZSB3b3VsZCBhIHNoYXJrIGhhdmUgdG8gYmUgaW4gb3JkZXIgdG8gZXF1YWwgdGhlIGJvZHkgbWFzc2VzIG9mICpEdW5rbGVvc3RldXMgdGVycmVsbGkqPw0KDQpgYGB7cn0NCmZpdC5zaGFya19ib2R5bWFzczwtbG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKGJvZHlfbWFzcyksDQogICAgICAgICAgICAgICAgICAgICAgIGRhdGFfZmluYWwlPiVmaWx0ZXIoY2xhZGU9PSJDaG9uZHJpY2h0aHllcyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIW9yZGVyICVpbiUgYygiUmhpbm9iYXRpZm9ybWVzIiwiUmFqaWZvcm1lcyIsIkNoaW1hZXJpZm9ybWVzIikpKQ0KZHVua193ZWlnaHRzJT4lDQogIGZpbHRlcih0YXhvbj09IkR1bmtsZW9zdGV1cyB0ZXJyZWxsaSIpJT4lDQogIG11dGF0ZShib2R5X21hc3M9Ym9keV9tYXNzKjEwMDApJT4lDQogIGF1Z21lbnQoZml0LnNoYXJrX2JvZHltYXNzLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdCIpJT4lDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfmV4cCguKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5zaGFya19ib2R5bWFzcykkQ0YpLA0KICAgICAgICAgZGlmZj0uZml0dGVkLXRvdGFsX2xlbmd0aCwNCiAgICAgICAgIGJvZHlfbWFzcz1yb3VuZChib2R5X21hc3MvMTAwMCwxKSklPiUNCiAgc2VsZWN0KHNwZWNpbWVuLHRvdGFsX2xlbmd0aCxib2R5X21hc3MsLmZpdHRlZCwubG93ZXIsLnVwcGVyLGRpZmYpJT4lDQogIGthYmxlKGRpZ2l0cz0xLGNvbC5uYW1lcz1jKCJTcGVjaW1lbiIsIlRvdGFsIExlbmd0aCIsIkVzdGltYXRlZCBNYXNzIChrZykiLCJFc3QuIiwiTG93ZXIgOTUlIFAuSS4iLCJVcHBlciA5NSUgUC5JLiIsIiUgRGlmZi4iKSxhbGlnbj0iYyIsDQogICAgICAgIGNhcHRpb249IlNpemUgb2Ygc2hhcmtzIChpbiBjbSkgbmVjZXNzYXJ5IHRvIGFjaGlldmUgd2VpZ2h0cyBzaW1pbGFyIHRvIGluZGl2aWR1YWxzIG9mIDxpPkR1bmtsZW9zdGV1cyB0ZXJyZWxsaTwvaT4sIHNob3dpbmcgbXVjaCBncmVhdGVyIHdlaWdodCByZWxhdGl2ZSB0byBsZW5ndGggb2YgbGF0dGVyIHRheG9uIiklPiUNCiAgYWRkX2hlYWRlcl9hYm92ZShjKCIgIj0zLCJFc3RpbWF0ZWQgVG90YWwgTGVuZ3RoIG9mIFNoYXJrIj0zLCIgIj0xKSklPiUNCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KTm90ZSB0aGF0IGFsdGhvdWdoIHRoZSBlc3RpbWF0ZWQgd2VpZ2h0IG9mIGEgc2hhcmsgdGhlIHNpemUgb2YgQ01OSCA1NzY4IGdldHMgdmVyeSBjbG9zZSB0byB0aGUgcmVwb3J0ZWQgdG90YWwgbGVuZ3RoIG9mIHRoZSBMYXVzYW5uZSAqQ2FyY2hhcm9kb24qIChEZSBNYWRkYWxlbmEgZXQgYWwuIDIwMDMpLCBsYW1uaWQgc2hhcmtzIGluIGdlbmVyYWwgaGF2ZSBhIG11Y2ggc2hvcnRlciBhbmQgaGVhdmllciBib2R5IHRoYW4gbW9zdCBzaGFya3MgZG8uIEFzIHRoaXMgcmVncmVzc2lvbiBlcXVhdGlvbiBpcyBiYXNlZCBvbiBhbGwgc2hhcmtzIChpLmUuLCBjYXJjaGFyaGluaWRzLCBoZXhhbmNoaWlkcywgY2V0b3JoaW5pZHMsIGV0Yy4pIENNTkggNTc2OCBpcyBzdGlsbCBleHBlY3RlZCB0byBiZSBtdWNoIHNtYWxsZXIgdGhhbiBhIGxhcmdlIGxhbW5pZCBsaWtlIE1aTCAyMzk4MS4NCg0KIyMgV2VpZ2h0IG9mIHRoZSBsYXJnZXN0IGtub3duIHNwZWNpbWVuIG9mICpEdW5rbGVvc3RldXMqIChDTU5IIDU5MzYpDQoNCmBgYHtyfQ0KIyBFc3RpbWF0ZWQgbGVuZ3RoIGZvciBDTU5IIDU5MzYgd2FzIGNhbGN1bGF0ZWQgYXMgdGhlIG1lYW4gb2YgdGhlIGVzdGltYXRlZCBsZW5ndGhzIHVzaW5nIEpNMyBhbmQgSk01IHVzaW5nIHRoZSBtb2RlbCAiZml0LnNoYXBlX2NsYWRlMyINCg0KQ01OSF81OTM2X2RpbWVuc2lvbnM8LWRhdGEuZnJhbWUoT09MPW1lYW4oYyhwcmVkaWN0KGZpdC5KTTUsZGF0YS5mcmFtZShKTTU9MzMuOSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJlZGljdChmaXQuSk0zLGRhdGEuZnJhbWUoSk0zPTE4LjYzKSkpKSwNCiAgICAgICAgICAgY2xhZGU9IlBsYWNvZGVybWkiLHNoYXBlPSJmdXNpZm9ybSIsaGFiaXRhdD0icGVsYWdpYyIsc3dpbWJsYWRkZXI9RixoZXRlcm9jZXJjYWw9VCwNCiAgICAgICAgICAgZmFtaWx5PSJEdW5rbGVvc3RlaWRhZSIsDQogICAgICAgICAgIGxlbmd0aDU3Njg9ZXhwKHByZWRpY3QoZml0LnNoYXBlX2NsYWRlMyxmb3NzaWxfdGF4YSU+JWZpbHRlcihzcGVjaW1lbj09IkNNTkggNTc2OCIpKSkqDQogICAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQuc2hhcGVfY2xhZGUzKSRDRiklPiUNCiAgbXV0YXRlKHRvdGFsX2xlbmd0aD1leHAocHJlZGljdChmaXQuc2hhcGVfY2xhZGUzLC4pKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5zaGFwZV9jbGFkZTMpJENGLA0KICAgICAgICAgZ2lydGg9dG90YWxfbGVuZ3RoKihmb3NzaWxfdGF4YSU+JWZpbHRlcihzcGVjaW1lbj09IkNNTkggNTc2OCIpJT4lcHVsbChnaXJ0aCkvDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoNTc2OCksDQogICAgICAgICBoZWFkX2xlbmd0aD10b3RhbF9sZW5ndGgqKGZvc3NpbF90YXhhJT4lZmlsdGVyKHNwZWNpbWVuPT0iQ01OSCA1NzY4IiklPiVwdWxsKGhlYWRfbGVuZ3RoKS8NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGg1NzY4KSklPiUNCiAgbXV0YXRlKHByZWNhdWRhbF9sZW5ndGg9ZXhwKHByZWRpY3QodG90YWxfbGVuZ3RoX3RvX3N0YW5kYXJkX2xlbmd0aCwuKSkqDQogICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHModG90YWxfbGVuZ3RoX3RvX3N0YW5kYXJkX2xlbmd0aCkkQ0YsDQogICAgICAgICBmb3JrX2xlbmd0aD1leHAocHJlZGljdCh0b3RhbF9sZW5ndGhfdG9fZm9ya19sZW5ndGgsLikpKg0KICAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKHRvdGFsX2xlbmd0aF90b19mb3JrX2xlbmd0aCkkQ0YpDQoNCnJiaW5kKHByZWRpY3QoZml0LmVsbGlwc29pZCxDTU5IXzU5MzZfZGltZW5zaW9ucyxpbnRlcnZhbD0icHJlZGljdCIpJT4lZXhwKCksDQogICAgICBwcmVkaWN0KGZpdC5lbGxpcHNvaWRfcGVsYWdpYyxDTU5IXzU5MzZfZGltZW5zaW9ucyxpbnRlcnZhbD0icHJlZGljdCIpJT4lZXhwKCkpJT4lDQogIGRhdGEuZnJhbWUoKSU+JQ0KICBhZGRfcm93KGZpdD1leHAocHJlZGljdChkdW5rX2xlbmd0aF93ZWlnaHQsQ01OSF81OTM2X2RpbWVuc2lvbnMpKSoNCiAgICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoZHVua19sZW5ndGhfd2VpZ2h0KSRDRiwNCiAgICAgICAgICBsd3I9ZXhwKHByZWRpY3QoZHVua19sZW5ndGhfd2VpZ2h0LENNTkhfNTkzNl9kaW1lbnNpb25zLGludGVydmFsPSJwcmVkaWN0aW9uIilbMl0pKg0KICAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhkdW5rX2xlbmd0aF93ZWlnaHQpJENGLA0KICAgICAgICAgIHVwcj1leHAocHJlZGljdChkdW5rX2xlbmd0aF93ZWlnaHQsQ01OSF81OTM2X2RpbWVuc2lvbnMsaW50ZXJ2YWw9InByZWRpY3Rpb24iKVszXSkqDQogICAgICAgICAgICByZWdyZXNzaW9uLnN0YXRzKGR1bmtfbGVuZ3RoX3dlaWdodCkkQ0YpJT4lDQogIG11dGF0ZShhY3Jvc3MoZXZlcnl0aGluZygpLH4uLzEwMDApKSU+JQ0KICBhZGRfcm93KGZpdD0NCiAgICAgIENNTkhfNTkzNl9kaW1lbnNpb25zJT4lDQogICAgICBtdXRhdGUod2VpZ2h0X2NhcmNoYXJvZG9uPTQ1Ljk4KigoZ2lydGgvMTAwKV4yKihwcmVjYXVkYWxfbGVuZ3RoLzEwMCkpXjAuOTI2NyklPiUNCiAgICAgICAgICAgICAgIHB1bGwod2VpZ2h0X2NhcmNoYXJvZG9uKSklPiUNCiAgcm93bmFtZXNfdG9fY29sdW1uKCklPiUNCiAgYWRkX2NvbHVtbihtb2RlbD1jKCJFbGxpcHNvaWQgTW9kZWwsIEFsbCBGaXNoZXMiLCJFbGxpcHNvaWQgTW9kZWwsIFBlbGFnaWMgRmlzaGVzIiwiRHVua2xlb3N0ZXVzIExlbmd0aC1XZWlnaHQgTW9kZWwiLCJDYXJjaGFyb2RvbiBMZW5ndGgtV2VpZ2h0IE1vZGVsIikpJT4lDQogIG11dGF0ZShyYW5nZT1pZmVsc2UoaXMubmEobHdyKSxOQSxwYXN0ZTAoIigiLHJvdW5kKGx3ciwxKSwi4oCTIixyb3VuZCh1cHIsMSksIikiKSkpJT4lDQogIGNvbHVtbl90b19yb3duYW1lcygibW9kZWwiKSU+JXNlbGVjdCgtcm93bmFtZSwtbHdyLC11cHIpJT4lDQogIGthYmxlKGRpZ2l0cz0xLGNvbC5uYW1lcz1jKCJFc3QuIiwiOTUlIFAuSS4iKSxhbGlnbj0iYyIsDQogICAgICAgIGNhcHRpb249IldlaWdodCBlc3RpbWF0ZXMgb2YgdGhlIGxhcmdlc3Qga25vd24gc3BlY2ltZW4gb2YgPGk+RHVua2xlb3N0ZXVzIHRlcnJlbGxpPC9pPiAoQ01OSCA1OTM2KSwgYXNzdW1pbmcgbWlzc2luZyBwcm9wb3J0aW9ucyB3ZXJlIHNpbWlsYXIgdG8gQ01OSCA1NzY4LiBBbGwgYm9keSBtYXNzZXMgaW4ga2cuIiklPiUNCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KIyBNaXNjZWxsYW5lb3VzIGFuYWx5c2VzDQoNCiMjIENhbGN1bGF0aW5nIHRoZSBsZW5ndGggb2YgQ01OSCA1NzY4IHVzaW5nICJlbnRlcmluZyBhbmdsZSBvZiB0aGUgYm9keSINCg0KT25lIG9mIHRoZSBmZXcgcHJldmlvdXMgbWV0aG9kcyB0byBleHBsaWNpdGx5IGVzdGltYXRlIHRvdGFsIGxlbmd0aCBpbiAqRHVua2xlb3N0ZXVzKiB1c2luZyBzdGF0aXN0aWNhbCBtZXRob2RzIG90aGVyIHRoYW4gc2NhbGluZyBvZmYgb2YgKkNvY2Nvc3RldXMqIHdhcyB0aGF0IG9mIEh1c3Nha29mIChbMTkwNl0oI3JlZmVyZW5jZXMpKSwgd2hvIGVzdGltYXRlZCB0b3RhbCBsZW5ndGggaW4gYSBqdXZlbmlsZSAqRHVua2xlb3N0ZXVzIHRlcnJlbGxpKiAoPSAiKkRpbmljaHRoeXMgaW50ZXJtZWRpdXMqIiBvZiB0aGlzIGF1dGhvcikgdXNpbmcg4oCcZW50ZXJpbmcgYW5nbGUgb2YgdGhlIGJvZHnigJ0uIFRoaXMgbWVhc3VyZW1lbnQgcmVwcmVzZW50cyB0aGUgYW50ZXJvcG9zdGVyaW9yIGxlbmd0aCBvZiB0aGUgYW5pbWFsIGZyb20gdGhlIGFudGVyaW9ybW9zdCBwb2ludCBvbiB0aGUgYm9keSB0byB0aGUgbG9jYXRpb24gb2YgZ3JlYXRlc3QgYm9keSBkZXB0aCwgZm9sbG93aW5nIERlYW4gKFsxOTAyXSgjcmVmZXJlbmNlcykpIGFuZCBQYXJzb25zIChbMTg4OF0oI3JlZmVyZW5jZXMpKS4gVGhlIGVudGVyaW5nIGFuZ2xlIG9mIHRoZSBib2R5IGVuZHMg4oCcd2l0aCB3b25kZXJmdWwgdW5pZm9ybWl0eeKAnSAoW1BhcnNvbnMsIDE4ODhdKCNyZWZlcmVuY2VzKSkgYXQgYXBwcm94aW1hdGVseSAzNiUgdGhlIHRvdGFsIGxlbmd0aCBvZiB0aGUgYW5pbWFsLCBhbmQgdGhpcyBhcHBlYXJzIHRvIGJlIGNvbnNpc3RlbnQgYWNyb3NzIG5la3RvbmljIGZpc2hlcyBhbmQgbWFyaW5lIHRldHJhcG9kcyAoPSBjZXRhY2VhbnMpLCBzdWdnZXN0aW5nIGEgYmlvbWVjaGFuaWNhbCByZWFzb24gZm9yIGl0cyBleGlzdGVuY2UuDQoNCkFzc3VtaW5nIHRoZSBhcGV4IG9mIHRoZSBtZWRpb2RvcnNhbCByZXByZXNlbnRzIHRoZSB0YWxsZXN0IHBvaW50IG9uIHRoZSBib2R5IGluICpEdW5rbGVvc3RldXMgdGVycmVsbGkqLCB0aGUg4oCcZW50ZXJpbmcgYW5nbGUgb2YgdGhlIGJvZHnigJ0gaW4gQ01OSCA1NzY4IGlzIGVzdGltYXRlZCB0byBtZWFzdXJlIDEyNeKAkzEyOCBjbSAoZGVwZW5kaW5nIG9uIHdoZXRoZXIgdGhpcyBpcyBtZWFzdXJlZCB0byB0aGUgYW50ZXJpb3IgZW5kIG9mIHRoZSBza3VsbCByb29mIG9yIHRoZSBwcm90cnVkaW5nIHN1cHJhZ25hdGhhbHMpLiBBc3N1bWluZyB0aGUg4oCcZW50ZXJpbmcgYW5nbGXigJ0gcmVwcmVzZW50cyAzNiUgb2YgdG90YWwgYm9keSBsZW5ndGggdGhlIGVzdGltYXRlZCB0b3RhbCBsZW5ndGggZm9yIENNTkggNTc2OCBpcyAzNDfigJMzNTUgY20sIHZlcnkgc2ltaWxhciB0byB0aGUgdG90YWwgbGVuZ3RocyBjYWxjdWxhdGVkIHZpYSBPT0wuIFRoaXMgaW5jcmVhc2VzIHRoZSBjb25maWRlbmNlIHRoYXQgT09MIGFjY3VyYXRlbHkgcHJlZGljdHMgdG90YWwgbGVuZ3RoIGluICpELiB0ZXJyZWxsaSouDQoNCiMjIFdpdGggb25seSBzcGVjaW1lbnMgbWVhc3VyZWQgZGlyZWN0bHkNCg0KQmVjYXVzZSBzbyBtdWNoIG9mIHRoZSBkYXRhIGNvbWVzIGZyb20gdGhlIHByZXZpb3VzbHkgcHVibGlzaGVkIGxpdGVyYXR1cmUsIHRoZXJlIGlzIGEgY29uY2VybiB0aGF0IGVycm9ycyBpbiBkYXRhIHJlcG9ydGluZyBtaWdodCBiaWFzIHJlc3VsdHMuIFRodXMsIGFuIGFuYWx5c2lzIHdhcyBwZXJmb3JtZWQgdXNpbmcgb25seSBzcGVjaW1lbnMgbWVhc3VyZWQgYnkgdGhlIGF1dGhvciBlaXRoZXIgZGlyZWN0bHkgb3IgZnJvbSBwdWJsaXNoZWQgcGhvdG9ncmFwaHMgaW4gdGhlIGxpdGVyYXR1cmUuDQoNCmBgYHtyfQ0KZml0LmVuZ2VsbWFuX21lYXN1cmVkPC1sbShsb2codG90YWxfbGVuZ3RoKX5sb2coT09MKSwNCiAgICAgICAgICAgICAgICAgZGF0YT1kYXRhX2ZpbmFsJT4lDQogIGZpbHRlcihgTWV0aG9kIG9mIE1lYXN1cmVtZW50YCAlaW4lIChjKCJtZWFzdXJlZCBmcm9tIGZpZ3VyZS9waG90byIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJtZWFzdXJlZCBmcm9tIHBob3RvIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm1lYXN1cmVkIGZyb20gc3BlY2ltZW4iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibWl4ZWQiKSkpKQ0KcmJpbmQoIkFsbCBTcGVjaW1lbnMiPXJlZ3Jlc3Npb24uc3RhdHMoZml0Lk9PTCksDQogICAgICAiU3BlY2ltZW5zIE1lYXN1cmVkIERpcmVjdGx5Ij1yZWdyZXNzaW9uLnN0YXRzKGZpdC5lbmdlbG1hbl9tZWFzdXJlZCkpDQpgYGANCg0KRXJyb3IgcmF0ZXMgb2YgdGhlIHR3byBtb2RlbHMgYXJlIGFib3V0IHRoZSBzYW1lLCBhbmQgdGhpcyBpcyBjb21wb3VuZGVkIGJ5IHRoZSBmYWN0IHRoYXQgbWFueSBvZiB0aGUgdGF4YSB0aGF0IGNvdWxkIG9ubHkgYmUgdGFrZW4gZnJvbSBsaXRlcmF0dXJlIGRhdGEgaW5jbHVkZXMgc29tZSBzcGVjaWVzIHdpdGggdW51c3VhbCBib2R5IHByb3BvcnRpb25zLCB3aGljaCBjb3VsZCBpbmZsYXRlIHRoZSBlcnJvciBvZiB0aGUgbW9kZWwgaW5jbHVkaW5nIGFsbCBzcGVjaW1lbnMgZXhhbWluZWQuDQoNCiMjIEVzdGltYXRpbmcgYm9keSBzaXplIGluICpEdW5rbGVvc3RldXMgdGVycmVsbGkqIHVzaW5nIChjbGFkZS1jb3JyZWN0ZWQpIG1vdXRoIGRpbWVuc2lvbnMNCg0KRmVycsOzbiBldCBhbC4gKDIwMTcpIGVzdGltYXRlZCBib2R5IHNpemUgaW4gKkR1bmtsZW9zdGV1cyB0ZXJyZWxsaSogdXNpbmcgbW91dGggZGltZW5zaW9ucyBpbiBzaGFya3MuIEhvd2V2ZXIsIHRoZXNlIG1ldGhvZCBkbyBub3QgYXBwZWFyIHRvIHJlbGlhYmx5IHByZWRpY3QgYm9keSBzaXplIGluIGFydGhyb2RpcmVzLCBkdWUgdG8gYXJ0aHJvZGlyZXMgaGF2aW5nIHByb3BvcnRpb25hbGx5IGxhcmdlciBtb3V0aHMgdGhhbiBzaGFya3MgKEVuZ2VsbWFuLCBpbiBwcmVzcykuIEhvd2V2ZXIsIHJ1bm5pbmcgYSByZWdyZXNzaW9uIG1vZGVsIHVzaW5nIG1vdXRoIHdpZHRoIGRhdGEgZm9yIGFydGhyb2RpcmVzIGFuZCBzaGFya3MgZnJvbSBFbmdlbG1hbiAoaW4gcHJlc3MpIGFuZCBhZGRpbmcgYW4gYWRkaXRpb25hbCBjb2VmZmljaWVudCB0byBjb250cm9sIGZvciBkaWZmZXJlbmNlcyBpbiBtb3V0aCBwcm9wb3J0aW9ucyBiZXR3ZWVuIGFydGhyb2RpcmVzIGFuZCBzaGFya3MgcHJvZHVjZXMgdGhlIGZvbGxvd2luZyByZXN1bHRz4oCmDQoNCmBgYHtyfQ0KZml0Lm1vdXRod2lkdGguY2xhZGVfY29ycmVjdGVkPC0NCiAgbG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKG1vdXRoX3dpZHRoKStjbGFkZSwNCiAgICAgZGF0YT1kYXRhX2ZpbmFsJT4lZmlsdGVyKGNsYWRlICVpbiUgYygiUGxhY29kZXJtaSIsIkNob25kcmljaHRoeWVzIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAhb3JkZXIgJWluJSBjKCJDaGltYWVyaWZvcm1lcyIsIlJhamlmb3JtZXMiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFnZW51cyAlaW4lIGMoIkFsb3BpYXMiKSklPiUNCiAgICAgICBmaWx0ZXIobGVuZ3RoX2FzPT0idG90YWwgbGVuZ3RoIikpDQpzdW1tYXJ5KGZpdC5tb3V0aHdpZHRoLmNsYWRlX2NvcnJlY3RlZCkNCmBgYA0KDQpCYXNlZCBvbiB0aGlzLCBhcnRocm9kaXJlcyBhbmQgc2hhcmtzIHNob3cgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQgbW91dGggc2l6ZXMsIGFuZCBpbmNsdWRpbmcgdGhlIGFkZGl0aW9uYWwgdGVybSBoYXMgYSBzaWduaWZpY2FudCBlZmZlY3Qgb24gdGhlIHJlc3VsdGluZyBwcmVkaWN0aW9ucy4NCg0KYGBge3J9DQpmb3NzaWxfdGF4YSU+JQ0KICBkcm9wX25hKG1vdXRoX3dpZHRoKSU+JQ0KICBmaWx0ZXIoISFmb3NzaWwuc3BlY2ltZW5zKSU+JQ0KICBtdXRhdGUodG90YWxfbGVuZ3RoPWlmZWxzZShnZW51cz09IkR1bmtsZW9zdGV1cyIsTkEsdG90YWxfbGVuZ3RoKSklPiUNCiAgYXVnbWVudChmaXQubW91dGh3aWR0aC5jbGFkZV9jb3JyZWN0ZWQsbmV3ZGF0YT0uLA0KICAgICAgICAgIGludGVydmFsPSJwcmVkaWN0aW9uIiklPiUNCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKg0KICAgICAgICAgICAgICAgICAgcmVncmVzc2lvbi5zdGF0cyhmaXQubW91dGh3aWR0aC5jbGFkZV9jb3JyZWN0ZWQpJENGKSwNCiAgICAgICAgIHJhbmdlPXBhc3RlMCgiKCIscm91bmQoLmxvd2VyLDEpLCLigJMiLHJvdW5kKC51cHBlciwxKSwiKSIpLA0KICAgICAgICAgUEU9cGFzdGUwKCIoIiwNCiAgICAgICAgICAgICAgICAgICByb3VuZCguZml0dGVkKigxLXJlZ3Jlc3Npb24uc3RhdHMoZml0Lm1vdXRod2lkdGguY2xhZGVfY29ycmVjdGVkKSRhZGpQRS8xMDApLA0KICAgICAgICAgICAgICAgICAgICAgICAgIDEpLA0KICAgICAgICAgICAgICAgICAgICLigJMiLA0KICAgICAgICAgICAgICAgICAgIHJvdW5kKC5maXR0ZWQqKDErcmVncmVzc2lvbi5zdGF0cyhmaXQubW91dGh3aWR0aC5jbGFkZV9jb3JyZWN0ZWQpJGFkalBFLzEwMCksDQogICAgICAgICAgICAgICAgICAgICAgICAgMSksDQogICAgICAgICAgICAgICAgICAgIikiDQogICAgICAgICAgICAgICAgICAgKSklPiUNCiAgcmVuYW1lKGZpdHRlZF9tb3V0aHdpZHRoPS5maXR0ZWQpJT4lDQogIGF1Z21lbnQoZml0LnNoYXBlX2NsYWRlMyxuZXdkYXRhPS4pJT4lDQogIHJlbmFtZShmaXR0ZWRfT09MPS5maXR0ZWQpJT4lDQogIG11dGF0ZShmaXR0ZWRfT09MPWV4cChmaXR0ZWRfT09MKSpyZWdyZXNzaW9uLnN0YXRzKGZpdC5zaGFwZV9jbGFkZTMpJENGKSU+JQ0KICBzZWxlY3QodGF4b24sIHNwZWNpbWVuLCB0b3RhbF9sZW5ndGgsIGZpdHRlZF9PT0wsIGZpdHRlZF9tb3V0aHdpZHRoLCBQRSwgcmFuZ2UpJT4lDQogIGthYmxlKGRpZ2l0cz0xLGFsaWduPWMoImwiLCJjIiwiYyIsImMiLCJjIiwiYyIsImMiKSwNCiAgICAgICAgY29sLm5hbWVzID0gYygiVGF4b24iLCJTcGVjaW1lbiIsIkFjdHVhbCBMZW5ndGgiLCJFc3RpbWF0ZWQgTGVuZ3RoIHVzaW5nIE9PTCIsIkVzdC4iLCIrLy0gJVBFIiwiOTUlIFAuSS4iKSwNCiAgICAgICAgY2FwdGlvbj0iTGVuZ2ggZXN0aW1hdGVzIGZvciA8aT5EdW5rbGVvc3RldXMgdGVycmVsbGk8L2k+IGFuZCBvdGhlciBhcnRocm9kaXJlcyB1c2luZyBtb3V0aCB3aWR0aCBhbmQgYSBjb21iaW5lZCBkYXRhc2V0IG9mIHNoYXJrcyBhbmQgYXJ0aHJvZGlyZXMsIHdpdGggYW4gYWRkaXRpb25hbCBjb2VmZmljaWVudCBmb3IgY2xhZGUgbWVtYmVyc2hpcCIpJT4lDQogIGFkZF9oZWFkZXJfYWJvdmUoYygiICI9NCwiRXN0aW1hdGluZyB1c2luZyBDbGFkZS1Db3JyZWN0ZWQgTW91dGggV2lkdGgiPTMpKSU+JQ0KICBjb2x1bW5fc3BlYygxLCBpdGFsaWMgPSBUKSU+JQ0KICBjb2x1bW5fc3BlYyg1LCBib2xkID0gVCklPiUNCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KVGh1cywgd2hlbiBjb3JyZWN0ZWQgZm9yIGRpZmZlcmVuY2VzIGluIG1vdXRoIHByb3BvcnRpb25zIGJldHdlZW4gYXJ0aHJvZGlyZXMgYW5kIHNoYXJrcywgbW91dGggd2lkdGggcHJvZHVjZXMgZXN0aW1hdGVzIG9mIGJvZHkgc2l6ZSBmb3IgKkR1bmtsZW9zdGV1cyB0ZXJyZWxsaSogdGhhdCBhcmUgY2xvc2UgdG8gZXN0aW1hdGVzIHVuZGVyIE9PTCwgdGhvdWdoIHNsaWdodGx5IGxvd2VyLg0KDQoocmVmOm1vdXRod2lkdGgpIEVzdGltYXRlZCBsZW5ndGggb2YgKkR1bmtsZW9zdGV1cyB0ZXJyZWxsaSogKGluIHdoaXRlKSB1c2luZyBtb3V0aCB3aWR0aCBtYWtpbmcgY29ycmVjdGlvbnMgZm9yIHRoZSB3aWRlciBtb3V0aHMgb2YgYXJ0aHJvZGlyZXMuDQoNCmBgYHtyLGZpZy5jYXA9IihyZWY6bW91dGh3aWR0aCkifQ0KZGF0YV9maW5hbCU+JQ0KICBmaWx0ZXIoY2xhZGUgJWluJSBjKCJDaG9uZHJpY2h0aHllcyIsIlBsYWNvZGVybWkiKSwNCiAgICAgICAgICFvcmRlciAlaW4lIGMoIkNoaW1hZXJpZm9ybWVzIiwiUmFqaWZvcm1lcyIpLA0KICAgICAgICAgZ2VudXMhPSJBbG9waWFzIiklPiUNCiAgZ2dwbG90KGFlcyh0b3RhbF9sZW5ndGgsbW91dGhfd2lkdGgpKSsNCiAgZ2VvbV9zdGFyKGFlcyhzdGFyc2hhcGU9Y2xhZGUsZmlsbD1jbGFkZSksDQogICAgICAgICAgICBkYXRhPS4lPiVmaWx0ZXIoZ2VudXMhPSJEdW5rbGVvc3RldXMiKSkrDQogIHNjYWxlX3hfY29udGludW91cyh0cmFucz0ibG9nMTAiKSsNCiAgc2NhbGVfeV9jb250aW51b3VzKHRyYW5zPSJsb2cxMCIpKw0KICBnZW9tX3Ntb290aChkYXRhPS4lPiUNCiAgICAgICAgICAgICAgICBtdXRhdGUoY2xhZGU9IkNob25kcmljaHRoeWVzIiklPiUNCiAgICAgICAgICAgICAgICBhdWdtZW50KGZpdC5tb3V0aHdpZHRoLmNsYWRlX2NvcnJlY3RlZCxuZXdkYXRhPS4pJT4lDQogICAgICAgICAgICAgICAgbXV0YXRlKC5maXR0ZWQ9ZXhwKC5maXR0ZWQpKnJlZ3Jlc3Npb24uc3RhdHMoZml0Lm1vdXRod2lkdGguY2xhZGVfY29ycmVjdGVkKSRDRiksDQogICAgICAgICAgICAgIGFlcyh4PS5maXR0ZWQseT1tb3V0aF93aWR0aCxjb2xvcj1jbGFkZSksZm9ybXVsYT15fngsbWV0aG9kPSJsbSIsc2U9RikrDQogIGdlb21fc21vb3RoKGRhdGE9LiU+JQ0KICAgICAgICAgICAgICAgIG11dGF0ZShjbGFkZT0iUGxhY29kZXJtaSIsISFmb3NzaWwuc3BlY2ltZW5zKSU+JQ0KICAgICAgICAgICAgICAgIGF1Z21lbnQoZml0Lm1vdXRod2lkdGguY2xhZGVfY29ycmVjdGVkLG5ld2RhdGE9LiklPiUNCiAgICAgICAgICAgICAgICBtdXRhdGUoLmZpdHRlZD1leHAoLmZpdHRlZCkqcmVncmVzc2lvbi5zdGF0cyhmaXQubW91dGh3aWR0aC5jbGFkZV9jb3JyZWN0ZWQpJENGKSwNCiAgICAgICAgICAgICAgYWVzKHg9LmZpdHRlZCx5PW1vdXRoX3dpZHRoLGNvbG9yPWNsYWRlKSxmb3JtdWxhPXl+eCxtZXRob2Q9ImxtIixzZT1GKSsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PTMwMCxsaW5ldHlwZT0iZGFzaGVkIikrDQogIGdlb21fc3RhcihhZXMoeD10b3RhbF9sZW5ndGgseT1tb3V0aF93aWR0aCxzdGFyc2hhcGU9Y2xhZGUsZmlsbD1jbGFkZSksDQogICAgICAgICAgICBkYXRhPWZvc3NpbF90YXhhICU+JSBmaWx0ZXIoZ2VudXM9PSJBbWF6aWNodGh5cyIpKSsNCiAgZ2VvbV9zdGFyKGFlcyh4PXRvdGFsX2xlbmd0aCx5PW1vdXRoX3dpZHRoLHN0YXJzaGFwZT1jbGFkZSxmaWxsPWNsYWRlKSwNCiAgICAgICAgICAgIGRhdGE9Zm9zc2lsX3RheGEgJT4lIGZpbHRlcihjbGFkZT09IlBsYWNvZGVybWkiLCEhZm9zc2lsLnNwZWNpbWVucykgJT4lDQogICAgICAgICAgICAgIGRyb3BfbmEodG90YWxfbGVuZ3RoKSAlPiUNCiAgICAgICAgICAgICAgZmlsdGVyKGdlbnVzIT0iRHVua2xlb3N0ZXVzIiksDQogICAgICAgICAgICBzaXplPTIsc2hvdy5sZWdlbmQ9RikrDQogIGdlb21fc3RhcihhZXMoeD0uZml0dGVkLHk9bW91dGhfd2lkdGgsc3RhcnNoYXBlPWNsYWRlKSwNCiAgICAgICAgICAgIGRhdGE9Zm9zc2lsX3RheGEgJT4lIGZpbHRlcihnZW51cz09IkR1bmtsZW9zdGV1cyIsISFmb3NzaWwuc3BlY2ltZW5zKSAlPiUNCiAgICAgICAgICAgICAgYXVnbWVudChmaXQubW91dGh3aWR0aC5jbGFkZV9jb3JyZWN0ZWQsbmV3ZGF0YT0uKSU+JQ0KICAgICAgICAgICAgICBtdXRhdGUoLmZpdHRlZD1leHAoLmZpdHRlZCkqDQogICAgICAgICAgICAgICAgICAgICAgIHJlZ3Jlc3Npb24uc3RhdHMoZml0Lm1vdXRod2lkdGguY2xhZGVfY29ycmVjdGVkKSRDRiksDQogICAgICAgICAgICBjb2xvcj0iYmxhY2siLGZpbGw9IndoaXRlIixzaXplPTMsc2hvdy5sZWdlbmQ9RikrDQogIGxhYnMoeD0iVG90YWwgTGVuZ3RoIChjbSkiLHk9Ik1vdXRoIFdpZHRoIChjbSkiLA0KICAgICAgIGNvbG9yPSJDbGFkZSIsc3RhcnNoYXBlPSJDbGFkZSIsZmlsbD0iQ2xhZGUiKSsNCiAgc2NhbGVfc3RhcnNoYXBlX21hbnVhbCh2YWx1ZXM9YygxNSwxKSkrDQogIGFubm90YXRlKGdlb209InRleHQiLHg9MjkwLHk9MS41LGhqdXN0PTEsbGFiZWw9IjMgbSIpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249YygwLjg1LDAuMikpDQpgYGANCg0KIyMgRXN0aW1hdGluZyBsZW5ndGggaW4gKkR1bmtsZW9zdGV1cyogdXNpbmcgYXJ0aHJvZGlyZXMgb25seQ0KDQpCZWNhdXNlIG9mIHRoZSB3aWRlIHZhcmlldHkgb2YgZmlzaGVzIGV4YW1pbmVkIGluIHRoaXMgc3R1ZHksIGl0IGlzIHdvcnRoIGV4YW1pbmluZyBhcnRocm9kaXJlcyBrbm93biBmcm9tIHdob2xlLWJvZHkgZm9zc2lscyBhbG9uZSB0byBzZWUgaWYgdXNpbmcgb25seSB0aGVzZSB0YXhhIHRvIGVzdGltYXRlIGxlbmd0aCBpbiAqRHVua2xlb3N0ZXVzKiBwcm9kdWNlcyBzaW1pbGFyIHJlc3VsdHMuDQoNCmBgYHtyfQ0KcGxhY29kZXJtc19vbmx5X09PTDwtbG0obG9nKHRvdGFsX2xlbmd0aCl+bG9nKE9PTCksDQogICAgICAgICAgICAgICAgICAgICAgICBkYXRhX2ZpbmFsJT4lZmlsdGVyKCEhZm9zc2lsLnNwZWNpbWVucyxnZW51cyE9IkR1bmtsZW9zdGV1cyIpKQ0KcGxhY29kZXJtc19vbmx5X2hlYWRsZW5ndGg8LWxtKGxvZyh0b3RhbF9sZW5ndGgpfmxvZyhoZWFkX2xlbmd0aCksDQogICAgICAgICAgICAgICAgICAgICAgICBkYXRhX2ZpbmFsJT4lZmlsdGVyKCEhZm9zc2lsLnNwZWNpbWVucyxnZW51cyE9IkR1bmtsZW9zdGV1cyIpKQ0KDQpyYmluZCgiVXNpbmcgSGVhZCBMZW5ndGgiPXJlZ3Jlc3Npb24uc3RhdHMocGxhY29kZXJtc19vbmx5X2hlYWRsZW5ndGgpLA0KICAgICAgIlVzaW5nIE9PTCI9cmVncmVzc2lvbi5zdGF0cyhwbGFjb2Rlcm1zX29ubHlfT09MKSkNCg0KZm9zc2lsX3RheGElPiUNCiAgZmlsdGVyKCEhZm9zc2lsLnNwZWNpbWVucyklPiUNCiAgbXV0YXRlKHRvdGFsX2xlbmd0aD1pZmVsc2UobGVuZ3RoX2FzPT0iZXN0aW1hdGVkIHQubC4iLE5BLHRvdGFsX2xlbmd0aCkpJT4lDQogIGF1Z21lbnQocGxhY29kZXJtc19vbmx5X2hlYWRsZW5ndGgsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0IiklPiUNCiAgbXV0YXRlKGFjcm9zcyguZml0dGVkOi51cHBlcix+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMocGxhY29kZXJtc19vbmx5X2hlYWRsZW5ndGgpJENGKSklPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCJmaXQxIiwuKSkpJT4lDQogIGF1Z21lbnQocGxhY29kZXJtc19vbmx5X09PTCxuZXdkYXRhPS4saW50ZXJ2YWw9InByZWRpY3QiKSU+JQ0KICBtdXRhdGUoYWNyb3NzKC5maXR0ZWQ6LnVwcGVyLH5leHAoLikqcmVncmVzc2lvbi5zdGF0cyhwbGFjb2Rlcm1zX29ubHlfT09MKSRDRikpJT4lDQogIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCcuJykpLCBmdW5zKHBhc3RlMCgiZml0MiIsLikpKSU+JQ0KICBtdXRhdGUocmFuZ2UxPXBhc3RlMCgiKCIscm91bmQoZml0MS5sb3dlciwxKSwiLSIscm91bmQoZml0MS51cHBlciwxKSwiKSIpLA0KICAgICAgICAgcmFuZ2UyPXBhc3RlMCgiKCIscm91bmQoZml0Mi5sb3dlciwxKSwiLSIscm91bmQoZml0Mi51cHBlciwxKSwiKSIpLA0KICAgICAgICAgUEUxX2xvd2VyPWZpdDEuZml0dGVkKigxLShyZWdyZXNzaW9uLnN0YXRzKHBsYWNvZGVybXNfb25seV9oZWFkbGVuZ3RoKSRhZGpQRSkvMTAwKSwNCiAgICAgICAgIFBFMV91cHBlcj1maXQxLmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhwbGFjb2Rlcm1zX29ubHlfaGVhZGxlbmd0aCkkYWRqUEUpLzEwMCksDQogICAgICAgICBQRTJfbG93ZXI9Zml0Mi5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMocGxhY29kZXJtc19vbmx5X09PTCkkYWRqUEUpLzEwMCksDQogICAgICAgICBQRTJfdXBwZXI9Zml0Mi5maXR0ZWQqKDErKHJlZ3Jlc3Npb24uc3RhdHMocGxhY29kZXJtc19vbmx5X09PTCkkYWRqUEUpLzEwMCksDQogICAgICAgICBQRV9yYW5nZTE9cGFzdGUwKCIoIixyb3VuZChQRTFfbG93ZXIsMSksIi0iLHJvdW5kKFBFMV91cHBlciwxKSwiKSIpLA0KICAgICAgICAgUEVfcmFuZ2UyPXBhc3RlMCgiKCIscm91bmQoUEUyX2xvd2VyLDEpLCItIixyb3VuZChQRTJfdXBwZXIsMSksIikiKSwNCiAgICAgICAgIFBFPSgoZml0MS5maXR0ZWQtdG90YWxfbGVuZ3RoKS9maXQxLmZpdHRlZCksDQogICAgICAgICBQRTI9KChmaXQyLmZpdHRlZC10b3RhbF9sZW5ndGgpL2ZpdDIuZml0dGVkKSklPiUNCiAgc2VsZWN0KHRheG9uLHNwZWNpbWVuLHRvdGFsX2xlbmd0aCwNCiAgICAgICAgIGZpdDEuZml0dGVkLFBFX3JhbmdlMSxyYW5nZTEsUEUsDQogICAgICAgICBmaXQyLmZpdHRlZCxQRV9yYW5nZTIscmFuZ2UyLFBFMiklPiUNCiAga2FibGUoZGlnaXRzPWMoMSwxLDEsMSwxLDEsMywxLDEsMSwzKSwNCiAgICAgICAgY29sLm5hbWVzPWMoIlRheG9uIiwiU3BlY2ltZW4iLCJBY3R1YWwiLCJFc3QuIiwiKy8tIFBFIiwiOTUlIFAuSS4iLCIlUEUiLA0KICAgICAgICAgICAgICAgICAgICAiRXN0LiIsIisvLSBQRSIsIjk1JSBDLkkuIiwiJVBFIiksDQogICAgICAgIGFsaWduPWMoImwiLCJjIiwiYyIsImMiLCJjIiwiYyIsImMiLCJjIiwiYyIsImMiLCJjIiksDQogICAgICAgIGNhcHRpb249Ikxlbmd0aCBlc3RpbWF0ZXMgZm9yIDxpPkR1bmtsZW9zdGV1cyB0ZXJyZWxsaTwvaT4gYW5kIGNvbXBsZXRlIGFydGhyb2RpcmVzIHVzaW5nIG9ubHkgZGF0YSBmcm9tIGFydGhyb2RpcmUgc3BlY2ltZW5zIHdpdGggd2hvbGUgYm9keSByZW1haW5zLiBBbGwgbWVhc3VyZW1lbnRzIGluIGNtLiIpJT4lDQogIGNvbHVtbl9zcGVjKDEsIGl0YWxpYyA9IFQpJT4lDQogIGNvbHVtbl9zcGVjKGMoNCw4KSwgYm9sZCA9IFQpJT4lDQogIGFkZF9oZWFkZXJfYWJvdmUoYygiICI9MywiVXNpbmcgSGVhZCBMZW5ndGgiPTQsIlVzaW5nIE9PTCI9NCkpJT4lDQogIGthYmxlX3N0eWxpbmcoKQ0KYGBgDQoNCkhlYWQgbGVuZ3RoIGFuZCBPT0wgYXJlIGJvdGggdmVyeSBhY2N1cmF0ZSBpbiBwcmVkaWN0aW5nIGxlbmd0aCBpbiBjb21wbGV0ZSBhcnRocm9kaXJlcyAobW9zdGx5IGNvY2Nvc3Rlb21vcnBocykuIEhvd2V2ZXIsIHRoZXkgcmVzdWx0IGluIGRyYW1hdGljIHVuZGVyZXN0aW1hdGVzIG9mIHRvdGFsIGxlbmd0aCBpbiAqQW1hemljaHRoeXMgdHJpbmFqc3RpY2FlKiwgbGlrZWx5IGJlY2F1c2Ugb2YgdGhlIHZlcnkgc21hbGwgaGVhZCBhbmQgc2lnbnMgb2YgYW4gZWxvbmdhdGUgYm9keSBwbGFuIGluIHRoaXMgdGF4b24gKHNlZSBiZWxvdyBhbmQgbWFudXNjcmlwdCkuDQoNClRoZSBlc3RpbWF0ZWQgbGVuZ3RocyBmb3IgKkR1bmtsZW9zdGV1cyogdW5kZXIgdGhlc2UgbW9kZWxzIGlzIHNpbWlsYXIgdG8gdGhlIGxlbmd0aHMgcHJlZGljdGVkIHVzaW5nIHRoZSBhbGwtZmlzaCBtb2RlbCwgc3BlY2lmaWNhbGx5IHRoZSBtb2RlbCB1c2luZyBoZWFkIGxlbmd0aCBieSBpdHNlbGYuIEhvd2V2ZXIsIGFzIG1lbnRpb25lZCBpbiB0aGUgbWFudXNjcmlwdCwgbGVuZ3RocyBvZiBsZXNzIHRoYW4gMyBtIGFyZSB1bmxpa2VseSBmb3IgQ01OSCA1NzY4IGJlY2F1c2UgdGhpcyB3b3VsZCByZXF1aXJlIGEgYm9keSBwbGFuIHdpdGggbmV4dCB0byBubyBjYXVkYWwgZmluIG9yIGNhdWRhbCBwZWR1bmNsZS4gSXQgaXMgbGlrZWx5IHRoZSBoZWFkIHNpemUgb2YgdGhlIEFjaGFubmFyYXMgY29jY29zdGVvbW9ycGhzIGlzIHNsaWdodGx5IGFib3ZlIGF2ZXJhZ2UgYmFzZWQgb24gdGhlaXIgcHJvcG9ydGlvbnMgKHNwZWNpZmljYWxseSwgdGhlIGxvY2F0aW9uIG9mIHRoZSBjcmFuaW8tdGhvcmFjaWMgam9pbnQpLCB3aGVyZWFzICpBbWF6aWNodGh5cyonIGhlYWQgc2l6ZSBpcyBiZWxvdyBhdmVyYWdlLCBlc3BlY2lhbGx5IGZvciBhcnRocm9kaXJlcy4NCg0KQWRkaXRpb25hbGx5LCBpdCBpcyBoaWdobHkgbGlrZWx5IHRoZSBtb2RlbCBpcyBiZWluZyBpbmZsdWVuY2VkIGJ5IGV4dHJhcG9sYXRpb24gZXJyb3IsIGFzIGl0IG11c3QgYmUgZXh0cmFwb2xhdGVkIHJvdWdobHkgZml2ZSB0aW1lcyB0aGUgc3BhbiBvZiB0aGUgZGF0YSBpbiBvcmRlciB0byBwcmVkaWN0IGJvZHkgc2l6ZSBpbiAqRHVua2xlb3N0ZXVzKi4gSXQgaXMgZm9yIHRoZXNlIHJlYXNvbnMgdGhhdCB0aGUgbW9kZWwgY29uc2lkZXJpbmcgb25seSBhcnRocm9kaXJlcyBpcyBub3QgZmF2b3JlZCBoZXJlLg0KDQpBZGRpdGlvbmFsbHksIGRlc3BpdGUgaGF2aW5nIGEgcGVyY2VudCBlcnJvciBvZiBvbmx5IGFib3V0IDglLCB0aGUgOTUlIHByZWRpY3Rpb24gaW50ZXJ2YWxzIGZvciB0aGUgbW9kZWwgYXJlIHZlcnkgbGFyZ2UsIGFsbW9zdCBhcyBsYXJnZSBhcyB0aGUgbW9kZWwgZml0IHVzaW5nIGFsbCBmaXNoZXMuIFRoaXMgaXMsIGFzIHN0YXRlZCBpbiB0aGUgbWFudXNjcmlwdCwgaW4gcGFydCBkdWUgdG8gdGhlIGJhY2stdHJhbnNmb3JtYXRpb24gb2YgZXJyb3JzIGZyb20gYSBsb2dhcml0aG1pYyBzY2FsZS4NCg0KQWxzbyBub3RlIHRoYXQgdGhlIGxlbmd0aCBlc3RpbWF0ZXMgaGVyZSBhcmUgbm90IHRoZSBzYW1lIGFzIHRoZSBlc3RpbWF0ZXMgY2l0ZWQgYXMgIlNpbXBsZSBzY2FsaW5nIGZyb20gKkNvY2Nvc3RldXMqLCBoZWFkIGxlbmd0aCIgaW4gdGhlIG1hbnVzY3JpcHQuIFRoYXQgaXMgY2FsY3VsYXRlZCBiYXNlZCBvbiB0aGUgcHJvcG9ydGlvbnMgb2YgdGhlIHJlY29uc3RydWN0aW9uIG9mICpDb2Njb3N0ZXVzIGN1c3BpZGF0dXMqIGluIE1pbGVzIGFuZCBXZXN0b2xsIChbMTk2OF0oI3JlZmVyZW5jZXMpKSwgcmF0aGVyIHRoYW4gYSByZWdyZXNzaW9uIGVxdWF0aW9uLg0KDQojIyMgU2NhbGluZyBvZiBoZWFkIHNpemUgaW4gQXJ0aHJvZGlyYQ0KDQpgYGB7cixmaWcuY2FwPSJQbG90IG9mIGhlYWQgc2l6ZSB2ZXJzdXMgdG90YWwgbGVuZ3RoIG9uIGEgKG5vbiBsb2ctdHJhbnNmb3JtZWQpIGRhdGFzZXQgb2YgYWxsIGFydGhyb2RpcmVzIHdpdGggY29tcGxldGUgcmVtYWlucyJ9DQpmb3NzaWxfdGF4YSAlPiUNCiAgZmlsdGVyKGxlbmd0aF9hcz09InRvdGFsIGxlbmd0aCIpICU+JQ0KICBtdXRhdGUodGF4b249aWZlbHNlKGdlbnVzPT0iTmV3c3BlY2llcyIsIkNNTkggYXNwaW5vdGhvcmFjaWQiLHRheG9uKSkgJT4lDQogIG11dGF0ZShjbGFkZTI9Y2FzZV93aGVuKGdlbnVzICVpbiUgYygiQW1hemljaHRoeXMiLCJOZXdzcGVjaWVzIil+IkFzcGlub3Rob3JhY2lkYSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGdlbnVzICVpbiUgYygiTWlsbGVyb3N0ZXVzIiwiRGlja29zdGV1cyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiV2F0c29ub3N0ZXVzIiwiQ29jY29zdGV1cyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiSW5jaXNvc2N1dHVtIiwiUGxvdXJkb3N0ZXVzIil+IkNvY2Nvc3Rlb21vcnBoYSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGdlbnVzICVpbiUgYygiQWZyaWNhbmFzcGlzIiwiSG9sb25lbWEiKX4iQmFzYWwgQXJ0aHJvZGlyYSIsKSkgJT4lDQogIGF1Z21lbnQobG0odG90YWxfbGVuZ3RofmhlYWRfbGVuZ3RoLC4pLG5ld2RhdGE9LiklPiUNCiAgZ2dwbG90KGFlcyhoZWFkX2xlbmd0aCx0b3RhbF9sZW5ndGgpKSsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSJsbSIsZm9ybXVsYT15fngsYWxwaGE9MC4yKSsNCiAgZ2VvbV9zdGFyKHN0YXJzaGFwZT0xNSxhZXMoZmlsbD1jbGFkZTIpKSsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD10YXhvbiwNCiAgICAgICAgICAgICAgICB5PXRvdGFsX2xlbmd0aCsNCiAgICAgICAgICAgICAgICAgIGlmZWxzZSgucmVzaWQ8MHxnZW51cyAlaW4lIGMoIkFtYXppY2h0aHlzIiwiTWlsbGVyb3N0ZXVzIiksLTIsMykrDQogICAgICAgICAgICAgICAgICBpZmVsc2UoZ2VudXMgJWluJSBjKCJNaWxsZXJvc3RldXMiKSwtMSwwKSsNCiAgICAgICAgICAgICAgICAgIGlmZWxzZShnZW51cyAlaW4lIGMoIkFmcmljYW5hc3BpcyIpLDEsMCksDQogICAgICAgICAgICAgICAgZm9udGZhY2U9aWZlbHNlKGdlbnVzPT0iTmV3c3BlY2llcyIsMSwzKSwNCiAgICAgICAgICAgICAgICBoanVzdD1pZmVsc2UoKC5yZXNpZD4wfGdlbnVzPT0iQW1hemljaHRoeXMiKSZnZW51cyE9Ik1pbGxlcm9zdGV1cyIsMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHNwZWNpbWVuICVpbiUgYygiQ01OSCA1MDIzMyIpLDAuMjUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2Uoc3BlY2ltZW4gJWluJSBjKCJOSE1VSyBQViBQIDQ5NjYzIiksMC41LDApKSkpLA0KICAgICAgICAgICAgc2l6ZT00KSsNCiAgbGFicyhmaWxsPSJBcnRocm9kaXJlIENsYWRlIix4PSJIZWFkIExlbmd0aCAoY20pIix5PSJUb3RhbCBMZW5ndGggKGNtKSIpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249YygwLjg1LDAuMikpDQpgYGANCg0KIyMgUmVzaWR1YWxzIG9mICpBbWF6aWNodGh5cyB0cmluYWpzdGljYWUqIHsjQW1hemljaHRoeXN9DQoNCmBgYHtyfQ0KT09MX3Jlc2lkdWFscyU+JQ0KICBtdXRhdGUoc2hhcGU9ZmFjdG9yKHNoYXBlLGxldmVscz1jKCJtYWNydXJpZm9ybSIsImFuZ3VpbGxpZm9ybSIsImVsb25nYXRlIiwiZnVzaWZvcm0iLCJjb21wcmVzc2lmb3JtIikpKSU+JQ0KICBmaWx0ZXIob250b2dlbnkgJWluJSBjKCJhZHVsdCIsInN1YmFkdWx0Iil8aXMubmEob250b2dlbnkpKSU+JQ0KICBncm91cF9ieSh0YXhvbiklPiUNCiAgc3VtbWFyaXNlKHJlc2lkdWFscz1tZWFuKHJlc2lkdWFscyksY2xhZGU9dW5pcXVlKGNsYWRlKSxnZW51cz11bmlxdWUoZ2VudXMpLHNoYXBlPXVuaXF1ZShzaGFwZSkpJT4lDQogIGZpbHRlcihnZW51cyAlaW4lIGMoIkFtYXppY2h0aHlzIiwiS2FqaWtpYSIsIklzdGlvbXBheCIsIkVzb3giLCJMb3hvZG9uIiwiQ2hpbG9zY3lsbGl1bSIsIlNwaHlyYWVuYSIsIlNjb21iZXJvaWRlcyIsIlRoeXJzaXRvaWRlcyIsIkFsYnVsYSIpKSU+JQ0KICBzZWxlY3QoIWdlbnVzKSU+JQ0KICBtdXRhdGUodGF4b249c3RyX3JlcGxhY2UodGF4b24sIl8iLCIgIikpJT4lDQogIGFycmFuZ2UoZGVzYyhjbGFkZSkpJT4lDQogIGthYmxlKGRpZ2l0cz0zLGFsaWduPWMoImwiLCJjIiwiYyIsImMiKSwNCiAgICAgICAgY29sLm5hbWVzPWMoIlRheG9uIiwiUmVzaWR1YWxzIiwiQ2xhZGUiLCJCb2R5IFNoYXBlIiksDQogICAgICAgIGNhcHRpb249IlJlc2lkdWFscyBvZiA8aT5BbWF6aWNodGh5cyB0cmluYWpzdGljYWU8L2k+IGNvbXBhcmVkIHRvIHNpbWlsYXIgZmlzaGVzLCBzaG93aW5nIGhvdyA8aT5BLiB0cmluYWpzdGljYWU8L2k+IGJlc3QgY29tcGFyZXMgdG8gZWxvbmdhdGUgb3IgJ3NlbWktZWxvbmdhdGUnIGZpc2hlcyB0aGF0IGhhdmUgc29tZSBkZWdyZWUgb2YgYXhpYWwgZWxvbmdhdGlvbiIpJT4lDQogIGNvbHVtbl9zcGVjKDEsIGl0YWxpYyA9IFQpJT4lDQogIHJvd19zcGVjKDEsIGJvbGQgPSBUKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQpCYXNlZCBvbiB0aGlzLCAqQW1hemljaHRoeXMqIGJlc3QgY29tcGFyZXMgdG8gdGF4YSB0aGF0IHNob3cgYSBzbGlnaHQgZGVncmVlIG9mIGF4aWFsIGVsb25nYXRpb24sIHN1Y2ggYXMgKkVzb3gqIHNwcC4sIGxhcmdlciBzcGVjaWVzIG9mICpTcGh5cmFlbmEqIHNwcC4sICpJc3Rpb21wYXggaW5kaWNhKiwgKkthamlraWEgYXVkYXgqIChidXQgbm90ICpLLiBhbGJpZGEqKSwgYW5kICpTY29tYmVyb2lkZXMgY29tbWVyc29ubmlhbnVzKi4NCg0KIyMgUmVsYXRpdmUgcGVsdmlzIHBvc2l0aW9uIGFjcm9zcyBub24tYWNhbnRob3B0ZXJ5Z2lhbiBmaXNoZXMNCg0KIyMjIFVzaW5nIGluZGl2aWR1YWwgb2JzZXJ2YXRpb25zDQoNCihyZWY6cGVsdmljaW5kaXZpZHVhbHMpICgqKkEqKikgUHJlcGVsdmljLWxlbmd0aCBhcyBhIHBlcmNlbnRhZ2Ugb2YgdG90YWwgbGVuZ3RoIGluIG5vbi1hY2FudGhvcHRlcnlnaWFuIGZpc2hlcywgc2hvd2luZyBob3cgdGhpcyB2YWx1ZSBpcyBhYm91dCA0NSUgb2YgdG90YWwgbGVuZ3RoLiAoKipCKiopIHRoZSBsZW5ndGggZnJvbSB0aGUgb3JpZ2luIG9mIHRoZSBwZWN0b3JhbCBhbmQgcGVsdmljIGZpbnMgYXMgYSBwZXJjZW50YWdlIG9mIHRoZSBsZW5ndGggZnJvbSB0aGUgb3JpZ2luIG9mIHRoZSBwZWN0b3JhbCBmaW4gdG8gdGhlIGJhc2Ugb2YgdGhlIGNhdWRhbCBwZWR1bmNsZSBpbiBub24tYWNhbnRob3B0ZXJ5Z2lhbiBmaXNoZXMsIHNob3dpbmcgaG93IHRoZSBwZWx2aWMgZmluIGlzIHBvc2l0aW9uZWQgYWJvdXQgbWlkd2F5IGJldHdlZW4gdGhlIG9yaWdpbiBvZiB0aGUgcGVjdG9yYWwgZmluIHRvIHRoZSBjYXVkYWwgcGVkdW5jbGUsIGVzcGVjaWFsbHkgaW4gc2hhcmtzIGFuZCBhcnRocm9kaXJlcy4gVGhlc2UgZ3JhcGhzIGNhbGN1bGF0ZWQgdXNpbmcgYWxsIHNwZWNpbWVucy4NCg0KYGBge3IsZmlnLmNhcD0iKHJlZjpwZWx2aWNpbmRpdmlkdWFscykiLGZpZy5hc3A9MS40NX0NCnBlbHZpY19sZW5ndGhzX2FsbHNwZWNpbWVuczwtZGF0YV9maW5hbCU+JQ0KICBtdXRhdGUocGVyY2VudF9wcmVwZWx2aWNfbGVuZ3RoPXByZXBlbHZpY19sZW5ndGgvdG90YWxfbGVuZ3RoLA0KICAgICAgICAgcGVjdG9yYWxfcGVsdmljX2xlbmd0aD1wcmVwZWx2aWNfbGVuZ3RoLXByZXBlY3RvcmFsX2xlbmd0aCwNCiAgICAgICAgIHBlY3RvcmFsX3ByZWNhdWRhbF9sZW5ndGg9cHJlY2F1ZGFsX2xlbmd0aC1wcmVwZWN0b3JhbF9sZW5ndGgsDQogICAgICAgICBoaWdoZXJfZ3JvdXA9ZmFjdG9yKGhpZ2hlcl9ncm91cCxsZXZlbHM9YygiUGV0cm9teXpvbnRpZm9ybWVzIiwiQXJ0aHJvZGlyYSIsIkNob25kcmljaHRoeWVzIiwiU2FyY29wdGVyeWdpaSIsIkJhc2FsIEFjdGlub3B0ZXJ5Z2lpIiwiQmFzYWwgVGVsZW9zdGVpIiwiT3N0YXJpb3BoeXNpIiwiU3RlbSBFdXRlbGVvc3RlaSIsIkFjYW50aG9wdGVyeWdpaSIpKSwNCiAgICAgICAgIG9yZGVyMj1pZmVsc2Uob3JkZXIgJWluJSBjKCJDYXJjaGFyaGluaWZvcm1lcyIsIlNxdWFsaWZvcm1lcyIpLA0KICAgICAgICAgICAgICAgICAgICAgICBmYW1pbHksb3JkZXIpKSU+JQ0KICBzZWxlY3QodGF4b24sc3BlY2ltZW4scmVmZXJlbmNlcyxwZXJjZW50X3ByZXBlbHZpY19sZW5ndGgscGVjdG9yYWxfcGVsdmljX2xlbmd0aCwNCiAgICAgICAgIHBlY3RvcmFsX3ByZWNhdWRhbF9sZW5ndGgsZXZlcnl0aGluZygpKSU+JQ0KICBhcnJhbmdlKGhpZ2hlcl9ncm91cCxvcmRlcix0YXhvbiklPiUNCiAgZmlsdGVyKGhpZ2hlcl9ncm91cCE9IkFjYW50aG9wdGVyeWdpaSIpDQpsZXZlbF9pbmZvXzIgPC0gcGVsdmljX2xlbmd0aHNfYWxsc3BlY2ltZW5zICU+JQ0KICBhcnJhbmdlKGhpZ2hlcl9ncm91cCwgb3JkZXIyKSAlPiUgDQogIHB1bGwob3JkZXIyKSAlPiUgDQogIHVuaXF1ZSgpDQpwZWx2aWNfbGVuZ3Roc19hbGxzcGVjaW1lbnM8LXBlbHZpY19sZW5ndGhzX2FsbHNwZWNpbWVucyU+JQ0KICBtdXRhdGUob3JkZXIyPWZhY3RvcihvcmRlcjIsbGV2ZWxzPXJldihsZXZlbF9pbmZvXzIpKSklPiUNCiAgZHJvcF9uYShwcmVwZWx2aWNfbGVuZ3RoLHRvdGFsX2xlbmd0aCxwcmVjYXVkYWxfbGVuZ3RoLHByZXBlY3RvcmFsX2xlbmd0aCkNCmdyaWQuYXJyYW5nZSgNCiAgZ2dwbG90KHBlbHZpY19sZW5ndGhzX2FsbHNwZWNpbWVucyxhZXMocGVyY2VudF9wcmVwZWx2aWNfbGVuZ3RoLG9yZGVyMikpKw0KICAgIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQ9bWVhbihwZXJjZW50X3ByZXBlbHZpY19sZW5ndGgpKSxsaW5ldHlwZT0iZGFzaGVkIikrDQogICAgZ2VvbV92aW9saW4oc2NhbGU9IndpZHRoIixhZXMoZmlsbD1oaWdoZXJfZ3JvdXApKSsNCiAgICBnZW9tX2JveHBsb3QoKSsNCiAgICBnZ3RpdGxlKCJBIikrDQogICAgY29vcmRfY2FydGVzaWFuKHhsaW09YygwLDEpKSsNCiAgICBsYWJzKHg9IlByZXBlbHZpYyBMZW5ndGggYXMgUGVyY2VudCBvZiBUb3RhbCBMZW5ndGgiLHk9Ik9yZGVyIikrDQogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiksDQogIGdncGxvdChwZWx2aWNfbGVuZ3Roc19hbGxzcGVjaW1lbnMlPiUNCiAgICAgICAgICAgZHJvcF9uYShwcmVjYXVkYWxfbGVuZ3RoLHByZXBlY3RvcmFsX2xlbmd0aCksDQogICAgICAgICBhZXMocGVjdG9yYWxfcGVsdmljX2xlbmd0aC9wZWN0b3JhbF9wcmVjYXVkYWxfbGVuZ3RoLG9yZGVyMikpKw0KICAgICAgICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PW1lYW4ocGVjdG9yYWxfcGVsdmljX2xlbmd0aC9wZWN0b3JhbF9wcmVjYXVkYWxfbGVuZ3RoKSxncm91cD1oaWdoZXJfZ3JvdXApLA0KICAgICAgICAgICAgICAgICAgIGxpbmV0eXBlPSJkYXNoZWQiKSsNCiAgICBnZW9tX3Zpb2xpbihzY2FsZT0id2lkdGgiLGFlcyhmaWxsPWhpZ2hlcl9ncm91cCkpKw0KICAgIGdlb21fYm94cGxvdCgpKw0KICAgIGdndGl0bGUoIkIiKSsNCiAgICBjb29yZF9jYXJ0ZXNpYW4oeGxpbT1jKDAsMSkpKw0KICAgIGxhYnMoeD0iUHJlcGVjdG9yYWwtUHJlcGVsdmljIExlbmd0aCBhcyBQZXJjZW50IG9mIFByZXBlY3RvcmFsLVByZWNhdWRhbCBMZW5ndGgiLHk9Ik9yZGVyIikrDQogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikpDQpgYGANCiANCiMjIyBVc2luZyBzcGVjaWVzIGF2ZXJhZ2VzDQogICANCihyZWY6cGVsdmljYXZlcmFnZXMpICgqKkEqKikgUHJlcGVsdmljLWxlbmd0aCBhcyBhIHBlcmNlbnRhZ2Ugb2YgdG90YWwgbGVuZ3RoIGluIG5vbi1hY2FudGhvcHRlcnlnaWFuIGZpc2hlcywgc2hvd2luZyBob3cgdGhpcyB2YWx1ZSBpcyBhYm91dCA0NSUgb2YgdG90YWwgbGVuZ3RoLiAoKipCKiopIHRoZSBsZW5ndGggZnJvbSB0aGUgb3JpZ2luIG9mIHRoZSBwZWN0b3JhbCBhbmQgcGVsdmljIGZpbnMgYXMgYSBwZXJjZW50YWdlIG9mIHRoZSBsZW5ndGggZnJvbSB0aGUgb3JpZ2luIG9mIHRoZSBwZWN0b3JhbCBmaW4gdG8gdGhlIGJhc2Ugb2YgdGhlIGNhdWRhbCBwZWR1bmNsZSBpbiBub24tYWNhbnRob3B0ZXJ5Z2lhbiBmaXNoZXMsIHNob3dpbmcgaG93IHRoZSBwZWx2aWMgZmluIGlzIHBvc2l0aW9uZWQgYWJvdXQgbWlkd2F5IGJldHdlZW4gdGhlIG9yaWdpbiBvZiB0aGUgcGVjdG9yYWwgZmluIHRvIHRoZSBjYXVkYWwgcGVkdW5jbGUsIGVzcGVjaWFsbHkgaW4gc2hhcmtzIGFuZCBhcnRocm9kaXJlcy4gVGhlc2UgZ3JhcGhzIGNhbGN1bGF0ZWQgdXNpbmcgc3BlY2llcyBhdmVyYWdlcy4NCiAgICANCmBgYHtyLGZpZy5jYXA9IihyZWY6cGVsdmljYXZlcmFnZXMpIixmaWcuYXNwPTEuNDV9DQpwZWx2aWNfbGVuZ3Roc19zcGVjaWVzYXZlcmFnZXM8LWRhdGFfZmluYWwgJT4lDQogIG11dGF0ZShvcmRlcjI9aWZlbHNlKG9yZGVyICVpbiUgYygiQ2FyY2hhcmhpbmlmb3JtZXMiLCJTcXVhbGlmb3JtZXMiKSxmYW1pbHksb3JkZXIpKSAlPiUNCiAgZHJvcF9uYShwcmVwZWx2aWNfbGVuZ3RoLHByZWNhdWRhbF9sZW5ndGgscHJlcGVjdG9yYWxfbGVuZ3RoLHRvdGFsX2xlbmd0aCkgJT4lDQogIGdyb3VwX2J5KHRheG9uKSAlPiUNCiAgc3VtbWFyaXNlKGFjcm9zcyhjKHByZXBlbHZpY19sZW5ndGgscHJlcGVjdG9yYWxfbGVuZ3RoLHByZWNhdWRhbF9sZW5ndGgsdG90YWxfbGVuZ3RoKSxtZWFuKSwNCiAgICAgICAgICAgIGFjcm9zcyhjKGNsYWRlLG9yZGVyMixoaWdoZXJfZ3JvdXApLHVuaXF1ZSkpICU+JQ0KICBtdXRhdGUocGVyY2VudF9wcmVwZWx2aWNfbGVuZ3RoPXByZXBlbHZpY19sZW5ndGgvdG90YWxfbGVuZ3RoLA0KICAgICAgICAgcGVjdG9yYWxfcGVsdmljX2xlbmd0aD1wcmVwZWx2aWNfbGVuZ3RoLXByZXBlY3RvcmFsX2xlbmd0aCwNCiAgICAgICAgIHBlY3RvcmFsX3ByZWNhdWRhbF9sZW5ndGg9cHJlY2F1ZGFsX2xlbmd0aC1wcmVwZWN0b3JhbF9sZW5ndGgsDQogICAgICAgICBoaWdoZXJfZ3JvdXA9ZmFjdG9yKGhpZ2hlcl9ncm91cCxsZXZlbHM9YygiUGV0cm9teXpvbnRpZm9ybWVzIiwiQXJ0aHJvZGlyYSIsIkNob25kcmljaHRoeWVzIiwiU2FyY29wdGVyeWdpaSIsIkJhc2FsIEFjdGlub3B0ZXJ5Z2lpIiwiQmFzYWwgVGVsZW9zdGVpIiwiT3N0YXJpb3BoeXNpIiwiU3RlbSBFdXRlbGVvc3RlaSIsIkFjYW50aG9wdGVyeWdpaSIpKSkgJT4lDQogIGFycmFuZ2UoaGlnaGVyX2dyb3VwLG9yZGVyMix0YXhvbikgJT4lDQogIGZpbHRlcihoaWdoZXJfZ3JvdXAhPSJBY2FudGhvcHRlcnlnaWkiKSAlPiUNCiAgbXV0YXRlKG9yZGVyMj1mYWN0b3Iob3JkZXIyLGxldmVscz1yZXYobGV2ZWxfaW5mb18yKSkpDQpncmlkLmFycmFuZ2UoDQogIGdncGxvdChwZWx2aWNfbGVuZ3Roc19zcGVjaWVzYXZlcmFnZXMsYWVzKHBlcmNlbnRfcHJlcGVsdmljX2xlbmd0aCxvcmRlcjIpKSsNCiAgICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PW1lYW4ocGVyY2VudF9wcmVwZWx2aWNfbGVuZ3RoKSksbGluZXR5cGU9ImRhc2hlZCIpKw0KICAgIGdlb21fdmlvbGluKHNjYWxlPSJ3aWR0aCIsYWVzKGZpbGw9aGlnaGVyX2dyb3VwKSkrDQogICAgZ2VvbV9ib3hwbG90KCkrDQogICAgY29vcmRfY2FydGVzaWFuKHhsaW09YygwLDEpKSsNCiAgICBsYWJzKHg9IlByZXBlbHZpYyBMZW5ndGggYXMgUGVyY2VudCBvZiBUb3RhbCBMZW5ndGgiLHk9Ik9yZGVyIikrDQogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiksDQogIGdncGxvdChwZWx2aWNfbGVuZ3Roc19zcGVjaWVzYXZlcmFnZXMlPiUNCiAgICAgICAgICAgZHJvcF9uYShwcmVjYXVkYWxfbGVuZ3RoLHByZXBlY3RvcmFsX2xlbmd0aCksDQogICAgICAgICBhZXMocGVjdG9yYWxfcGVsdmljX2xlbmd0aC9wZWN0b3JhbF9wcmVjYXVkYWxfbGVuZ3RoLG9yZGVyMikpKw0KICAgIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQ9bWVhbihwZWN0b3JhbF9wZWx2aWNfbGVuZ3RoL3BlY3RvcmFsX3ByZWNhdWRhbF9sZW5ndGgpLA0KICAgICAgICAgICAgICAgICAgIGdyb3VwPWhpZ2hlcl9ncm91cCksDQogICAgICAgICAgICAgICAgICAgbGluZXR5cGU9ImRhc2hlZCIpKw0KICAgIGdlb21fdmlvbGluKHNjYWxlPSJ3aWR0aCIsYWVzKGZpbGw9aGlnaGVyX2dyb3VwKSkrDQogICAgZ2VvbV9ib3hwbG90KCkrDQogICAgY29vcmRfY2FydGVzaWFuKHhsaW09YygwLDEpKSsNCiAgICBsYWJzKHg9IlByZXBlY3RvcmFsLVByZXBlbHZpYyBMZW5ndGggYXMgUGVyY2VudCBvZiBQcmVwZWN0b3JhbC1QcmVjYXVkYWwgTGVuZ3RoIix5PSJPcmRlciIpKw0KICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpKQ0KYGBgDQoNCkJhc2VkIG9uIHRoaXMsIGl0IGlzIGNsZWFyIHRoYXQgdGhlIHBlbHZpYyBnaXJkbGUgaXMgdHlwaWNhbGx5IGxvY2F0ZWQgYWJvdXQgbWlkd2F5IGFsb25nIHRoZSBhbmltYWzigJlzIGJvZHkgaW4gbW9zdCBub24tYWNhbnRob3B0ZXJ5Z2lhbiBmaXNoZXMsIHBhcnRpY3VsYXJseSB0aG9zZSB3aXRoIHVuc3BlY2lhbGl6ZWQgYm9keSBzaGFwZXMuIFRoZXNlIGluY2x1ZGUgIENhcmNoYXJoaW5pZm9ybWVzLCBIZXhhbmNoaWZvcm1lcywgc29tZSBTcXVhbGlmb3JtZXMgKGkuZS4sIFNxdWFsaWRhZSksIE9yZWN0b2xvYmlmb3JtZXMsIEFjdGluaXN0aWEsIEFtaWlmb3JtZXMsIEVsb3BpZm9ybWVzLCBldGMuIFRoaXMgYWxzbyBhcHBlYXJzIHRvIGJlIHRoZSBjYXNlIGZvciBhcnRocm9kaXJlcyB3aGljaCBhcmUga25vd24gZnJvbSBjb21wbGV0ZSByZW1haW5zIChpLmUuLCAqTWlsbGVyb3N0ZXVzKiwgKkNvY2Nvc3RldXMqLCAqV2F0c29ub3N0ZXVzKiwgKkluY2lzb3NjdXR1bSosICpBbWF6aWNodGh5cyosIGV0Yy4pIA0KDQpBZGRpdGlvbmFsbHksIHRoZSBwZWx2aWMgZ2lyZGxlIHRlbmRzIHRvIGJlIG1pZHdheSBiZXR3ZWVuIHRoZSBvcmlnaW4gb2YgdGhlIHBlY3RvcmFsIGdpcmRsZSBhbmQgdGhlIGJhc2Ugb2YgdGhlIHRhaWwgZmluIGluIG1vc3Qgbm9uLWFjYW50aG9wdGVyeWdpYW4gZmlzaGVzLCB0aG91Z2ggdGhlIGdyb3VwcyBtb3JlIGNsb3NlbHkgcmVsYXRlZCB0byBBY2FudGhvcHRlcnlnaWkgdGVuZCB0byBzaG93IG1vcmUgYWNhbnRob3B0ZXJ5Z2lhbi1saWtlIHByb3BvcnRpb25zLiBBZ2FpbiwgdGhpcyBpbmNsdWRlcyBhcnRocm9kaXJlcyBmb3Igd2hpY2ggY29tcGxldGUgYm9keSBmb3NzaWwgYXJlIGtub3duLiBHaXZlbiB0aGF0IHRoZSBwZWx2aWMgZ2lyZGxlIGlzIHVzdWFsbHkganVzdCBwb3N0ZXJpb3IgdG8gdGhlIHZlbnRyYWwgZW5kIG9mIHRoZSBhcm1vciBpbiBhcnRocm9kaXJlcywgYSBwcmVkaWN0ZWQgdmFsdWUgb2YgYWJvdXQgfjE1NS0xNjAgY20gaW4gQ01OSCA1NzY4LCB0aGlzIGZ1cnRoZXIgc3VnZ2VzdHMgbGVuZ3RocyBvZiBhYm91dCAzLjYgbSBmb3IgdHlwaWNhbCBhZHVsdCBpbmRpdmlkdWFscyBvZiAqRHVua2xlb3N0ZXVzIHRlcnJlbGxpKg0KDQojIyBMZW5ndGggb2YgKlRpdGFuaWNodGh5cyoNCg0KYGBge3IsY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZSd9DQpmaXQuZmlsdGVyZmVlZGVyPC1sbShsb2codG90YWxfbGVuZ3RoKX5sb2coT09MKSwNCiAgICAgICAgICAgICAgICAgICAgZGF0YV9pbml0aWFsJT4lZmlsdGVyKGdlbnVzICVpbiUgYygiTWVnYWNoYXNtYSIsIkNldG9yaGludXMiLCJSaGluY29kb24iKSkpDQojIEp1dmVuaWxlIHNwZWNpbWVucyBmb3IgdGhlc2UgZ2VuZXJhIHdlcmUgaW5jbHVkZWQgYXMgb3RoZXJ3aXNlIHRoZXJlIHdlcmUgbm8gc3BlY2ltZW5zIHNtYWxsZXIgdGhhbiB+NDAwIGNtIHRvdGFsIGxlbmd0aA0KDQpmb3NzaWxfdGF4YSU+JQ0KICBmaWx0ZXIoZ2VudXM9PSJUaXRhbmljaHRoeXMiKSU+JQ0KICBhdWdtZW50KGZpdC5zaGFwZV9jbGFkZTMsbmV3ZGF0YT0uLGludGVydmFsPSJwcmVkaWN0aW9uIiklPiUNCiAgbXV0YXRlKGFjcm9zcyhjKC5maXR0ZWQ6LnVwcGVyKSx+ZXhwKC4pKnJlZ3Jlc3Npb24uc3RhdHMoZml0LnNoYXBlX2NsYWRlMykkQ0YpKSAlPiUNCiAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoJy4nKSksIGZ1bnMocGFzdGUwKCJmaXQxIiwuKSkpJT4lDQogIGF1Z21lbnQoZml0LmZpbHRlcmZlZWRlcixuZXdkYXRhPS4saW50ZXJ2YWw9InByZWRpY3Rpb24iKSU+JQ0KICBtdXRhdGUoYWNyb3NzKGMoLmZpdHRlZDoudXBwZXIpLH5leHAoLikqcmVncmVzc2lvbi5zdGF0cyhmaXQuZmlsdGVyZmVlZGVyKSRDRikpICU+JQ0KICByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgnLicpKSwgZnVucyhwYXN0ZTAoImZpdDIiLC4pKSklPiUNCiAgbXV0YXRlKFBFXzE9cGFzdGUwKCIoIixyb3VuZChmaXQxLmZpdHRlZCooMS0ocmVncmVzc2lvbi5zdGF0cyhmaXQuc2hhcGVfY2xhZGUzKSRhZGpQRS8xMDApKSwxKSwNCiAgICAgICAgICAgICAgICAgICAgICLigJMiLHJvdW5kKGZpdDEuZml0dGVkKigxKyhyZWdyZXNzaW9uLnN0YXRzKGZpdC5zaGFwZV9jbGFkZTMpJGFkalBFLzEwMCkpLDEpLCIpIiksDQogICAgICAgICByYW5nZV8xPXBhc3RlMCgiKCIscm91bmQoZml0MS5sb3dlciwxKSwi4oCTIixyb3VuZChmaXQxLnVwcGVyLDEpLCIpIiksDQogICAgICAgICBQRV8yPXBhc3RlMCgiKCIscm91bmQoZml0Mi5maXR0ZWQqKDEtKHJlZ3Jlc3Npb24uc3RhdHMoZml0LmZpbHRlcmZlZWRlcikkYWRqUEUvMTAwKSksMSksDQogICAgICAgICAgICAgICAgICAgICAi4oCTIixyb3VuZChmaXQyLmZpdHRlZCooMSsocmVncmVzc2lvbi5zdGF0cyhmaXQuZmlsdGVyZmVlZGVyKSRhZGpQRS8xMDApKSwxKSwiKSIpLA0KICAgICAgICAgcmFuZ2VfMj1wYXN0ZTAoIigiLHJvdW5kKGZpdDIubG93ZXIsMSksIuKAkyIscm91bmQoZml0Mi51cHBlciwxKSwiKSIpKSU+JQ0KICBzZWxlY3Qoc3BlY2ltZW4sT09MLGZpdDEuZml0dGVkLFBFXzEscmFuZ2VfMSxmaXQyLmZpdHRlZCxQRV8yLHJhbmdlXzIpICU+JQ0KICBrYWJsZShkaWdpdHM9MSxhbGlnbj1jKCJsIiwiYyIsImMiLCJjIiwiYyIpLA0KICAgICAgICBjb2wubmFtZXM9YygiVGF4b24iLCJTcGVjaW1lbiIsIkVzdC4iLCIrLy1QRSIsIjk1JSBQLkkuIiwiRXN0LiIsIisvLVBFIiwiOTUlIFAuSS4iKSwNCiAgICAgICAgY2FwdGlvbj0iTGVuZ3RoIGVzdGltYXRlcyBmb3IgPGk+VGl0YW5pY2h0aHlzIGNsYXJraTwvaT4gdXNpbmcgdGhlIG1vZGVsIHdpdGggYWRkaXRpb25hbCBjb2VmZmljaWVudHMgZm9yIHNoYXBlIGFuZCB2YXJpYWJsZSBzbG9wZXMgYmV0d2VlbiBjaG9uZHJpY2h0aHlhbnMgYW5kIG5vbi1jaG9uZHJpY2h0aHlhbnMgKGZpdC5zaGFwZV9jbGFkZTMpIiklPiUNCiAgYWRkX2hlYWRlcl9hYm92ZShjKCIgIj0yLCJBbGwgVGF4YSI9MywiRmlsdGVyLUZlZWRpbmcgc2hhcmtzIE9ubHkiPTMpKSU+JQ0KICBjb2x1bW5fc3BlYyhjKDMsNiksIGJvbGQgPSBUKSU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQpBTU5IIEZGIDcxMzQgaXMgdGhlIHZlcnkgbGFyZ2UgaW5kaXZpZHVhbCBvZiAqVGl0YW5pY2h0aHlzKiB3aGljaCwgYXQgdGhlIHRpbWUgb2YgdGhpcyB3cml0aW5nLCBpcyBtb3VudGVkIGluIHRoZSBBbWVyaWNhbiBNdXNldW0gb2YgTmF0dXJhbCBIaXN0b3J5J3MgSGFsbCBvZiBWZXJ0ZWJyYXRlIE9yaWdpbnMuIENNTkggNTAzMTkgaXMgdGhlIGFzc29jaWF0ZWQgc3ViYWR1bHQgaW5kaXZpZHVhbCBkZXNjcmliZWQgYnkgQm95bGUgYW5kIFJ5YW4gKFsyMDE3XSgjcmVmZXJlbmNlcykpLiBSZW1haW5zIG9mICpUaXRhbmljaHRoeXMqIHRlbmQgdG8gYmUgZnJhZ21lbnRhcnkgYW5kIG11Y2ggbGVzcyBjb21tb24gdGhhbiB0aG9zZSBvZiAqRHVua2xlb3N0ZXVzKiwgb2Z0ZW4gZnJhZ21lbnRzIG9ubHkgYXNzaWduZWQgdG8gKlRpdGFuaWNodGh5cyogYmFzZWQgb24gYXJtb3IgdGhpbm5lc3MgYW5kIHRleHR1cmUgKFtCb3lsZSBhbmQgUnlhbiAyMDE3XSgjcmVmZXJlbmNlcyksIEQuIENoYXBtYW4gcGVycy4gY29tbS4pLCBidXQgYWxsIHRoZSBzcGVjaW1lbnMgb2YgKlRpdGFuaWNodGh5cyogdGhlIGF1dGhvciBoYXMgZXhhbWluZWQgYXJlIGVpdGhlciBzaW1pbGFyIGluIHNpemUgdG8gQU1OSCBGRiA3MTM0IG9yIGFyZSBtdWNoIHNtYWxsZXIgaW4gc2l6ZSAoZS5nLiwgdGhvc2UgZmlndXJlZCBpbiBbRHVua2xlIGFuZCBCdW5nYXJ0IDE5NDJdKCNyZWZlcmVuY2VzKSkuDQoNCkFzIGNhbiBiZSBzZWVuIGZyb20gdGhlc2UgcmVzdWx0cywgKlRpdGFuaWNodGh5cyogaXMgcm91Z2hseSBjb21wYXJhYmxlIGluIHNpemUgdG8gKkR1bmtsZW9zdGV1cyosIGFjY291bnRpbmcgZm9yIHRoZSBmYWN0IHRoYXQgdGhlcmUgYXJlIGZld2VyIGluZGl2aWR1YWxzIGtub3duIGZyb20gYW5hdG9taWNhbGx5IGlkZW50aWZpYWJsZSBtYXRlcmlhbC4NCg0KIyMgQm9keSBtYXNzIG9mICpSaGl6b2R1cyBoaWJiZXJ0aSoNCg0KYGBge3IsY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZSd9DQpkYXRhLmZyYW1lKHRvdGFsX2xlbmd0aD01NjMscHJlY2F1ZGFsX2xlbmd0aD00NTYuNzgscm93bmFtZT0iVXNpbmcgcHJvcG9ydGlvbnMgb2YganV2ZW5pbGUgU3RyZXBzb2R1cyIsaGFiaXRhdD0iYmVudGhpYyIsDQogICAgICAgICAgIGJvZHlfZGVwdGg9ODkuNTUsYm9keV93aWR0aD04OS41NSxoZWFkX2xlbmd0aD0xMDQuMTQsDQogICAgICAgICAgIHN3aW1ibGFkZGVyPVQsaGV0ZXJvY2VyY2FsPVQpJT4lDQogIGFkZF9yb3codG90YWxfbGVuZ3RoPTUxNCxwcmVjYXVkYWxfbGVuZ3RoPTQxNyxyb3duYW1lPSJVc2luZyB0cnVuayBwcm9wb3J0aW9ucyBvZiBHb29sb25nb25naWEiLGhhYml0YXQ9ImJlbnRoaWMiLA0KICAgICAgICAgIGJvZHlfZGVwdGg9NzYuMTYsYm9keV93aWR0aD03Ni4xNixoZWFkX2xlbmd0aD0xMDQuMTQsDQogICAgICAgICAgc3dpbWJsYWRkZXI9VCxoZXRlcm9jZXJjYWw9VCklPiUNCiAgbXV0YXRlKGdpcnRoPXJhbWFudWphbi5hcHByb3goYm9keV9kZXB0aCxib2R5X3dpZHRoKSklPiUNCiAgYXVnbWVudChmaXQuZWxsaXBzb2lkLG5ld2RhdGE9LixpbnRlcnZhbD0icHJlZGljdCIpJT4lDQogIG11dGF0ZShhY3Jvc3MoLmZpdHRlZDoudXBwZXIsfihleHAoLikqcmVncmVzc2lvbi5zdGF0cyhmaXQuZWxsaXBzb2lkKSRDRi8xMDAwKSkpJT4lDQogIGNvbHVtbl90b19yb3duYW1lcygicm93bmFtZSIpJT4lDQogIHNlbGVjdCh0b3RhbF9sZW5ndGgsLmZpdHRlZCwubG93ZXIsLnVwcGVyKSU+JQ0KICBrYWJsZShkaWdpdHM9MSxhbGlnbj0iYyIsDQogICAgICAgIGNvbC5uYW1lcz1jKCJFc3RpbWF0ZWQgTGVuZ3RoIChjbSkiLCJFc3QuIiwiTG93ZXIgOTUlIFAuSS4iLCJVcHBlciA5NSUgUC5JLiIpLA0KICAgICAgICBjYXB0aW9uPSJFc3RpbWF0ZWQgYm9keSBtYXNzZXMgb2YgPGk+Umhpem9kdXMgaGliYmVydGk8L2k+IHVzaW5nIGRpbWVuc2lvbnMgb2YgdGhlIGp1dmVuaWxlIDxpPlN0cmVwc29kdXMgYW5jdWxvbmFtZW5zaXM8L2k+IGZyb20gQW5kcmV3cyAoMTk4NSkgYW5kIHRydW5rIHByb3BvcnRpb25zIG9mIDxpPkdvb2xvbmdvbmdpYSBsb29tZXNpPC9pPiBmcm9tIEpvaGFuc29uIGFuZCBBaGxiZXJnICgxOTk4KSAoaS5lLiwgdHJ1bmsgZGVwdGggYW5kIHRvdGFsIGxlbmd0aCBidXQgdHJlYXRpbmcgdHJ1bmtzIGFzIGN5bGluZHJpY2FsIGJlY2F1c2UgRGV2b25pYW4gcmhpem9kb250cyB1bmxpa2UgQ2FyYm9uaWZlcm91cyBvbmVzIGFyZSBkb3Jzb3ZlbnRyYWxseSBjb21wcmVzc2VkOyBKZWZmZXJ5LCBwZXJzLiBjb21tLikiKSU+JQ0KICBhZGRfaGVhZGVyX2Fib3ZlKGMoIiAiPTIsIkVzdGltYXRlZCBCb2R5IE1hc3MgKGtnKSI9MykpJT4lDQogIGthYmxlX3N0eWxpbmcoKQ0KYGBgDQoNCkl0IGlzIHBvc3NpYmxlIHRoZXNlIGVzdGltYXRlcyBtaWdodCBiZSBvdmVyZXN0aW1hdGVzIGlmICpSaGl6b2R1cyogZGlkIG5vdCBoYXZlIGEgcGVyZmVjdGx5IGN5bGluZHJpY2FsIHRydW5rIGFuZCBpbnN0ZWFkIGhhZCBhIHRydW5rIHRoYXQgd2FzIHNsaWdodGx5IGRlZXBlciB0aGFuIHdpZGUuDQoNCiMgUmVmZXJlbmNlcyB7I3JlZmVyZW5jZXN9DQoNCjxkaXYgc3R5bGU9InRleHQtaW5kZW50OiAtNDBweDsgcGFkZGluZy1sZWZ0OiA0MHB4OyBtYXJnaW4tYm90dG9tPTA7IG1hcmdpbi10b3A9MCI+DQpBZnJpc2FsLCBNLjsgTnVyamlyYW5hOyBJcm1hd2F0aTsgQnVyaGFudWRkaW4sIEEuSS4gT3N0ZW9sb2dpY2FsIHN0dWR5IG9mIFRpdGFuIFRyaWdnZXIgZmlzaCwgKkJhbGlzdG9pZGVzIHZpcmlkZXNjZW5zKiAoQmxvY2ggYW5kIFNjaG5laWRlciwgMTgwMSkgKEJhbGlzdGlkYWU6IFRldHJhb2RvbnRpZm9ybWVzKSBmcm9tIHRoZSBTcGVybW9uZGUgQXJjaGlwZWxhZ28gV2F0ZXJzLiAqSU9QIENvbmZlcmVuY2UgU2VyaWVzOiBFYXJ0aCBhbmQgRW52aXJvbm1lbnRhbCBTY2llbmNlKiAqKjIwMTkqKiwgMzcwLCBkb2k6MTAuMTA4OC8xNzU1LTEzMTUvMzcwLzEvMDEyMDM1Lg0KDQpBbGV4YW5kZXIsIFIuTS4gKkZ1bmN0aW9uYWwgRGVzaWduIGluIEZpc2hlcyo7IEh1dGNoaW5zb24gVW5pdmVyc2l0eSBMaWJyYXJ5OiBMb25kb24sICoqMTk2NyoqLg0KDQpBdWx0LCBKLlMuOyBMdW8sIEouIEEgcmVsaWFibGUgZ2FtZSBmaXNoIHdlaWdodCBlc3RpbWF0aW9uIG1vZGVsIGZvciBBdGxhbnRpYyB0YXJwb24gKCpNZWdhbG9wcyBhdGxhbnRpY3VzKikuICpGaXNoZXJpZXMgUmVzZWFyY2gqICoqMjAxMyoqLCAxMzk6MTEwLTExNy4gZG9pOmh0dHBzOi8vZG9pLm9yZy8xMC4xMDE2L2ouZmlzaHJlcy4yMDEyLjEwLjAwNA0KDQpCYWtlciBDLkYuOyBSb3NzaSBDLlIuOyBRdWlyb2dhIFAuOyBXaGl0ZSBFLjsgV2lsbGlhbXMgUC47IEtpdHNvbiwgSjsgQmljZSwgQy5NLjsgUmVuYXVkLCBDLkIuOyBQb3R0ZXIsIEkuOyBOZWlyYSwgRi5KLjsgYW5kIEJhaWfDum4sIEMuIE1vcnBob21ldHJpYyBhbmQgcGh5c2ljYWwgY2hhcmFjdGVyaXN0aWNzIGRpc3Rpbmd1aXNoaW5nIGFkdWx0IFBhdGFnb25pYW4gbGFtcHJleSwgKkdlb3RyaWEgbWFjcm9zdG9tYSogZnJvbSB0aGUgcG91Y2hlZCBsYW1wcmV5LCAqR2VvdHJpYSBhdXN0cmFsaXMqLiAqUExvUyBPTkUqICoqMjAxOSoqLCAxNig1KTogZTAyNTA2MDEuIGh0dHBzOi8vZG9pLm9yZy8xMC4xMzcxL2pvdXJuYWwucG9uZS4wMjUwNjAxDQoNCkJhbGRyaWRnZSwgSC5ELiBTaW5raW5nIEZhY3RvcnMgYW5kIEF2ZXJhZ2UgRGVuc2l0aWVzIG9mIEZsb3JpZGEgU2hhcmtzIGFzIEZ1bmN0aW9ucyBvZiBMaXZlciBCdW95YW5jeS4gKkNvcGVpYSogKioxOTcwKiosIDE5NzAsIDc0NC03NTQsIGRvaToxMC4yMzA3LzE0NDIzMTcuDQoNCkJsYW50b24sIFAuTC47IEJpZ2dzLCBOLkwuIERlbnNpdHkgb2YgZnJlc2ggYW5kIGVtYmFsbWVkIGh1bWFuIGNvbXBhY3QgYW5kIGNhbmNlbGxvdXMgYm9uZS4gKkFtZXJpY2FuIEpvdXJuYWwgb2YgUGh5c2ljYWwgQW50aHJvcG9sb2d5KiAqKjE5NjgqKiwgMjksIDM5LTQ0LCBkb2k6aHR0cHM6Ly9kb2kub3JnLzEwLjEwMDIvYWpwYS4xMzMwMjkwMTEzLg0KDQpCb25lLCBRLiBCdW95YW5jeSBhbmQgSHlkcm9keW5hbWljIEZ1bmN0aW9ucyBvZiBJbnRlZ3VtZW50IGluIHRoZSBDYXN0b3IgT2lsIEZpc2gsIDhSdXZldHR1cyBwcmV0aW9zdXMqIChQaXNjZXM6IEdlbXB5bGlkYWUpLiAqQ29wZWlhKiAqKjE5NzIqKiwgMjIsIDEzNS0xNTYuIGRvaToxMC4xMDE3L3Njcy4yMDE3LjEyDQoNCkJveWxlLCBKLjsgUnlhbiwgTS5KLiBOZXcgaW5mb3JtYXRpb24gb24gKlRpdGFuaWNodGh5cyogKFBsYWNvZGVybWksIEFydGhyb2RpcmEpIGZyb20gdGhlIENsZXZlbGFuZCBTaGFsZSAoVXBwZXIgRGV2b25pYW4pIG9mIE9oaW8sIFVTQS4gKkogUGFsZW9udG9sKiAqKjIwMTcqKiwgOTEsIDMxOOKAkzMzNiwgZG9pOjEwLjEwMTcvanBhLjIwMTYuMTM2Lg0KDQpCcmFzc2V5LCBDLkEuIEJvZHktbWFzcyBlc3RpbWF0aW9uIGluIHBhbGVvbnRvbG9neTogYSByZXZpZXcgb2Ygdm9sdW1ldHJpYyB0ZWNobmlxdWVzLiAqVGhlIFBhbGVvbnRvbG9naWNhbCBTb2NpZXR5IFBhcGVycyogKioyMDE2KiosIDIyLCAxMzUtMTU2LiBkb2k6MTAuMTAxNy9zY3MuMjAxNy4xMg0KDQpDYXJyLCBSLksuIFBhbGVvZWNvbG9neSBvZiAqRHVua2xlb3N0ZXVzIHRlcnJlbGxpKi4gKktpcnRsYW5kaWEqICoqMjAxMCoqLCA1NywgMzYtNDUuDQoNCkNvbGxldHRlIEJCLCBhbmQgTmF1ZW4gQ0UuICpTY29tYnJpZHMgb2YgdGhlIHdvcmxkOiBhbiBhbm5vdGF0ZWQgYW5kIGlsbHVzdHJhdGVkIGNhdGFsb2d1ZSBvZiB0dW5hcywgbWFja2VyZWxzLCBib25pdG9zLCBhbmQgcmVsYXRlZCBzcGVjaWVzIGtub3duIHRvIGRhdGUqLiBGb29kIGFuZCBBZ3JpY3VsdHVyZSBPcmdhbml6YXRpb24gb2YgdGhlIFVuaXRlZCBOYXRpb25zOiBSb21lLCAqKjE5ODMqKi4NCg0KQ29tcGFnbm8sIEwuSi5WLiAqU2hhcmtzIG9mIHRoZSB3b3JsZC4gQW4gYW5ub3RhdGVkIGFuZCBpbGx1c3RyYXRlZCBjYXRhbG9ndWUgb2Ygc2hhcmsgc3BlY2llcyBrbm93biB0byBkYXRlLiBQYXJ0IDEuIEhleGFuY2hpZm9ybWVzIHRvIExhbW5pZm9ybWVzKiBGb29kIGFuZCBBZ3JpY3VsdHVyZSBPcmdhbml6YXRpb24gb2YgdGhlIFVuaXRlZCBOYXRpb25zOiBSb21lLCAqKjE5ODQqKi4NCg0KQ29ybmVyLCBFLkQuUy47IERlbnRvbiwgRS5KLjsgRm9yc3RlciwgRy4gT24gdGhlIGJ1b3lhbmN5IG9mIHNvbWUgZGVlcC1zZWEgc2hhcmtzLiAqUHJvY2VlZGluZ3Mgb2YgdGhlIFJveWFsIFNvY2lldHkgb2YgTG9uZG9uLiBTZXJpZXMgQi4gQmlvbG9naWNhbCBTY2llbmNlcyogKioxOTY5KiosIDE3MSwgNDE1LTQyOSwgZG9pOjEwLjEwOTgvcnNwYi4xOTY5LjAwMDMNCg0KQ3VwZWxsbywgQy47IEJyaXRvLCBQLk0uOyBIZXJiaW4sIE0uOyBNZXVuaWVyLCBGLkouOyBKYW52aWVyLCBQLjsgRHV0ZWwsIEguOyBDbMOpbWVudCwgRy4gQWxsb21ldHJpYyBncm93dGggaW4gdGhlIGV4dGFudCBjb2VsYWNhbnRoIGx1bmcgZHVyaW5nIG9udG9nZW5ldGljIGRldmVsb3BtZW50LiAqTmF0dXJlIENvbW11bmljYXRpb25zKiAqKjIwMTUqKiwgNiwgODIyMiwgZG9pOjEwLjEwMzgvbmNvbW1zOTIyMi4NCg0KRGVhbiwgQi4gQmlvbWV0cmljIEV2aWRlbmNlIGluIHRoZSBQcm9ibGVtIG9mIHRoZSBQYWlyZWQgTGltYnMgb2YgdGhlIFZlcnRlYnJhdGVzLiAqQW1lcmljYW4gTmF0dXJhbGlzdCogKioxOTAyKiosIDM2LCA4MzctODQ3DQoNCkRlYW4sIEIuIE5vdGVzIG9uIGEgbmV3bHkgbW91bnRlZCAqVGl0YW5pY2h0aHlzKi4gKk1lbW9pcnMgb2YgdGhlIEFtZXJpY2FuIE11c2V1bSBvZiBOYXR1cmFsIEhpc3RvcnkqICoqMTkwOSoqLCA1LCAyNzDigJMyNzEuDQoNCkRlIE1hZGRhbGVuYSwgQS47IEdsYWl6b3QsIE8uOyBPbGl2aWVyLCBHLiBPbiB0aGUgR3JlYXQgV2hpdGUgU2hhcmssICpDYXJjaGFyb2RvbiBjYXJjaGFyaWFzKiAoTGlubmFldXMsIDE3NTgpLCBwcmVzZXJ2ZWQgaW4gdGhlIE11c2V1bSBvZiBab29sb2d5IGluIExhdXNhbm5lLiAqTWFyaW5lIExpZmUqICoqMjAwMyoqLCAxMywgNTMtNTkuDQoNCkR1bmtsZSwgRC4gSC47IEJ1bmdhcnQsIFAuIEEuIFRoZSBpbmZlcm8tZ25hdGhhbCBwbGF0ZXMgb2YgKlRpdGFuaWNodGh5cyouICpTY2llbnRpZmljIFB1YmxpY2F0aW9ucyBvZiB0aGUgQ2xldmVsYW5kIE11c2V1bSBvZiBOYXR1cmFsIEhpc3RvcnkqICoqMTk0MioqLCA4LCA0OS01OS4NCg0KRW5nZWxtYW4sIFIuSy4gR2lhbnQsIHN3aW1taW5nIG1vdXRoczogb3JhbCBkaW1lbnNpb25zIG9mIGV4dGFudCBzaGFya3MgZG8gbm90IGFjY3VyYXRlbHkgcHJlZGljdCBib2R5IHNpemUgaW4gKkR1bmtsZW9zdGV1cyB0ZXJyZWxsaSogIChQbGFjb2Rlcm1pOiBBcnRocm9kaXJhKS4gKipJbiBQcmVzcyoqLg0KDQpFbmdlbG1hbiwgUi5LLiBSZWNvbnN0cnVjdGlvbiBvZiAqRHVua2xlb3N0ZXVzIHRlcnJlbGxpKiAoUGxhY29kZXJtaTogQXJ0aHJvZGlyYSk6IGEgbmV3IGxvb2sgZm9yIHRoZSBEZXZvbmlhbuKAmXMgbW9zdCBmYW1vdXMgcHJlZGF0b3IuICoqSW4gUHJlcCoqLg0KDQpGZXJyw7NuLCBILkcuOyBNYXJ0w61uZXotUMOpcmV6LCBDLjsgQm90ZWxsYSwgSC4gRWNvbW9ycGhvbG9naWNhbCBpbmZlcmVuY2VzIGluIGVhcmx5IHZlcnRlYnJhdGVzOiByZWNvbnN0cnVjdGluZyAqRHVua2xlb3N0ZXVzIHRlcnJlbGxpKiAoQXJ0aHJvZGlyYSwgUGxhY29kZXJtaSkgY2F1ZGFsIGZpbiBmcm9tIHBhbGFlb2Vjb2xvZ2ljYWwgZGF0YS4gKlBlZXJKKiAqKjIwMTcqKiwgNSwgZTQwODEsIGRvaToxMC43NzE3L3BlZXJqLjQwODENCg0KRnJlZWRtYW4sIEouQS47IE5vYWtlcywgRC5MLkcuIFdoeSBhcmUgdGhlcmUgbm8gcmVhbGx5IGJpZyBib255IGZpc2hlcz8gQSBwb2ludC1vZi12aWV3IG9uIG1heGltdW0gYm9keSBzaXplIGluIHRlbGVvc3RzIGFuZCBlbGFzbW9icmFuY2hzLiAqUmV2aWV3cyBpbiBGaXNoIEJpb2xvZ3kgYW5kIEZpc2hlcmllcyogKioyMDAyKiosIDEyLCA0MDMtNDE2LCBkb2k6aHR0cHM6Ly9kb2kub3JnLzEwLjEwMjMvQToxMDI1MzY1MjEwNDE0DQoNCkZyb2VzZSwgUi4gQ3ViZSBsYXcsIGNvbmRpdGlvbiBmYWN0b3IgYW5kIHdlaWdodOKAk2xlbmd0aCByZWxhdGlvbnNoaXBzOiBoaXN0b3J5LCBtZXRhLWFuYWx5c2lzIGFuZCByZWNvbW1lbmRhdGlvbnMuICpKb3VybmFsIG9mIEFwcGxpZWQgSWNodGh5b2xvZ3kqICoqMjAwNioqLCAyMiwgMjQxLTI1MywgZG9pOmh0dHBzOi8vZG9pLm9yZy8xMC4xMTExL2ouMTQzOS0wNDI2LjIwMDYuMDA4MDUueC4NCg0KR291amV0LCBELiDigJxMdW5nc+KAnSBpbiBQbGFjb2Rlcm1zLCBhIHBlcnNpc3RlbnQgcGFsYWVvYmlvbG9naWNhbCBteXRoIHJlbGF0ZWQgdG8gZW52aXJvbm1lbnRhbCBwcmVjb25jZWl2ZWQgaW50ZXJwcmV0YXRpb25zLiAqQ29tcHRlcyBSZW5kdXMgUGFsZXZvbCogKioyMDExKiosIDEwLCAzMjPigJMzMjksIGRvaTpodHRwczovL2RvaS5vcmcvMTAuMTAxNi9qLmNycHYuMjAxMS4wMy4wMDguDQoNCkh1c3Nha29mLCBMLiBOb3RlcyBvbiB0aGUgRGV2b25pYW4gInBsYWNvZGVybSIgKkRpbmljaHRoeXMgaW50ZXJtZWRpdXMqIE5ld2IuICpCdWxsZXRpbiBvZiB0aGUgQW1lcmljYW4gTXVzZXVtIG9mIE5hdHVyYWwgSGlzdG9yeSogKioxOTA1KiosIDIxLCAyNy0zNi4NCg0KS2F1ZmZtYW4sIEQuRS4gTm90ZXMgb24gdGhlIGJpb2xvZ3kgb2YgdGhlIHRpZ2VyIHNoYXJrICgqR2FsZW9jZXJkbyBhcmN0aWN1cyopIGZyb20gUGhpbGlwcGluZSB3YXRlcnM7IDE2OyBVLiBTLiBGaXNoICYgV2lsZGxpZmUgU2VydmljZTogV2FzaGluZ3RvbiBELkMuLCAqKjE5NTAqKjsgcHAuIDEtIDEwLg0KDQpJQ1JQLiBCYXNpYyBhbmF0b21pY2FsIGFuZCBwaHlzaW9sb2dpY2FsIGRhdGE6IFRoZSBza2VsZXRvbi4gKkFubmFscyBvZiB0aGUgSUNSUCogKioxOTk1KiosIDI1LCAxLTgwLCBkb2k6MTAuMTAxNi9TMDE0Ni02NDUzKDAwKTgwMDA0LTQuDQoNCk1jQ3VuZSwgQS5SLjsgQ2FybHNvbiwgUi5MLiBUd2VudHkgd2F5cyB0byBsb3NlIHlvdXIgYmxhZGRlcjogY29tbW9uIG5hdHVyYWwgbXV0YW50cyBpbiB6ZWJyYWZpc2ggYW5kIHdpZGVzcHJlYWQgY29udmVyZ2VuY2Ugb2Ygc3dpbSBibGFkZGVyIGxvc3MgYW1vbmcgdGVsZW9zdCBmaXNoZXMuICpFdm9sdXRpb24gYW5kIERldmVsb3BtZW50KiAqKjIwMDQqKiwgNiwgMjQ2LTI1OSwgZG9pOmh0dHBzOi8vZG9pLm9yZy8xMC4xMTExL2ouMTUyNS0xNDJYLjIwMDQuMDQwMzAueC4NCg0KTWlsZXMsIFIuUy47IFdlc3RvbGwsIFQuUy4gVGhlIFBsYWNvZGVybSBGaXNoICpDb2Njb3N0ZXVzIGN1c3BpZGF0dXMqIE1pbGxlciBleCBBZ2Fzc2l6IGZyb20gdGhlIE1pZGRsZSBPbGQgUmVkIFNhbmRzdG9uZSBvZiBTY290bGFuZC4gUGFydCBJLiBEZXNjcmlwdGl2ZSBNb3JwaG9sb2d5LiAqVHJhbnNhY3Rpb25zIG9mIHRoZSBSb3lhbCBTb2NpZXR5IG9mIEVkaW5idXJnaCogKioxOTY4KiosICo2NyosIDM3My00NzYsIGRvaToxMC4xMDE3L1MwMDgwNDU2ODAwMDI0MDc4Lg0KDQpNaWxsZXIsIFIuRy4gKkJleW9uZCBBTk9WQSwgQmFzaWNzIG9mIEFwcGxpZWQgU3RhdGlzdGljcyo7IEpvaG4gV2lsZXkgYW5kIFNvbnM6IE5ldyBZb3JrIDE5ODY7IHAuIDMxNy4NCg0KTW9sbGV0LCBILkYuOyBDYWlsbGlldCwgRy5NLiBVc2luZyBhbGxvbWV0cnkgdG8gcHJlZGljdCBib2R5IG1hc3MgZnJvbSBsaW5lYXIgbWVhc3VyZW1lbnRzIG9mIHRoZSB3aGl0ZSBzaGFyay4gSW4gKkdyZWF0IFdoaXRlIFNoYXJrczogVGhlIEJpb2xvZ3kgb2YgKkNhcmNoYXJvZG9uIGNhcmNoYXJpYXMsIEtsaW1sZXksIEEuUC4sIEFpbmxleSwgRC5HLiwgRWRzLjsgQWNhZGVtaWMgUHJlc3M6IE5ldyBZb3JrLCAqKjE5OTYqKjsgcHAuIDgxLTg5Lg0KDQpQYXJzb25zLiBEaXNwbGFjZW1lbnQgYW5kIEFyZWEgQ3VydmVzIG9mIEZpc2hlcy4gKlRyYW5zYWN0aW9ucyBvZiB0aGUgQW1lcmljYW4gU29jaWV0eSBvZiBNZWNoYW5pY2FsIEVuZ2luZWVycyogKioxODg4KiosIDksIDY3OS02OTUuDQoNClJhbWFudWphbiwgUy4gKlJhbWFudWphbuKAmXMgQ29sbGVjdGVkIFdvcmtzKjsgQ2hlbHNlYSwgTmV3IFlvcmssICoqMTk2MioqLg0KDQpSb2JpbnMsIEMuUi4gU3VtbWVyIGNvbmNlbnRyYXRpb24gb2Ygd2hpdGUgbWFybGluLCAqVGV0cmFwdHVydXMgYWxiaWR1cyosIHdlc3Qgb2YgdGhlIHN0cmFpdHMgb2YgR2licmFsdGFyLiBJbiAqUHJvY2VlZGluZ3Mgb2YgdGhlIGludGVybmF0aW9uYWwgYmlsbGZpc2ggc3ltcG9zaXVtIEthaWx1YS1Lb25hLCBIYXdhaWksIDktMTIgQXVndXN0IDE5NzIuIFBhcnQgMi4gUmV2aWV3IGFuZCBDb250cmlidXRlZCBQYXBlcnMqLCBTaG9tdXJhLCBTLiwgV2lsbGlhbXMsIEYuLCBFZHMuOyBOYXRpb25hbCBPY2VhbmljIGFuZCBBdG1vc3BoZXJpYyBBZG1pbmlzdHJhdGlvbiwgTk9BQSBUZWNobmljYWwgUmVwb3J0IE5NRlMgU1NSRi02NzU6IDE5NzQ7IHBwLiAxNjQtMTc0Lg0KDQpSb2JpbnMsIEMuUi47IERlIFN5bHZhLCBELlAuIEEgbmV3IHdlc3Rlcm4gQXRsYW50aWMgc3BlYXJmaXNoLCAqVGV0cmFwdHVydXMgcGZsdWVnZXJpKiwgd2l0aCBhIHJlZGVzY3JpcHRpb24gb2YgdGhlIE1lZGl0ZXJyYW5lYW4gc3BlYXJmaXNoICpUZXRyYXB0dXJ1cyBiZWxvbmUqLiAqQnVsbGV0aW4gb2YgTWFyaW5lIFNjaWVuY2Ugb2YgdGhlIEd1bGYgYW5kIENhcmliYmVhbiogKioxOTYzKiosIDEzLCA4NeKAkzEyMi4NCg0KU2NodWx0emUsIEguLVAuIEp1dmVuaWxlIHNwZWNpbWVucyBvZiAqRXVzdGhlbm9wdGVyb24gZm9vcmRpKiBXaGl0ZWF2ZXMsIDE4ODEgKG9zdGVvbGVwaWZvcm0gcmhpcGlkaXN0aWFuLCBQaXNjZXMpIGZyb20gdGhlIExhdGUgRGV2b25pYW4gb2YgTWlndWFzaGEsIFF1ZWJlYywgQ2FuYWRhLiAqSm91cm5hbCBvZiBWZXJ0ZWJyYXRlIFBhbGVvbnRvbG9neSogKioxOTg0KiosIDQsIDEtMTYuIGh0dHBzOi8vZG9pLm9yZy8xMC4xMDgwLzAyNzI0NjM0LjE5ODQuMTAwMTE5ODINCg0KU2hlcGhhcmQsIFIuSi4gKkJvZHkgQ29tcG9zaXRpb24gaW4gQmlvbG9naWNhbCBBbnRocm9wb2xvZ3kqOyBDYW1icmlkZ2UgVW5pdmVyc2l0eSBQcmVzczogQ2FtYnJpZGdlLCAqKjE5OTEqKjsgcC4gMzY4Lg0KDQpTcHJpbmdlciwgUy47IEdpbGJlcnQsIFAuVy4gVGhlIEJhc2tpbmcgU2hhcmssICpDZXRvcmhpbnVzIG1heGltdXMqLCBmcm9tIEZsb3JpZGEgYW5kIENhbGlmb3JuaWEsIHdpdGggQ29tbWVudHMgb24gSXRzIEJpb2xvZ3kgYW5kIFN5c3RlbWF0aWNzLiAqQ29wZWlhKiAqKjE5NzYqKiwxLCA0Ny01NC4gaHR0cHM6Ly9kb2kub3JnLzEwLjIzMDcvMTQ0Mzc3MA0KDQpUcmluYWpzdGljLCBLLiBOZXcgYW5hdG9taWNhbCBpbmZvcm1hdGlvbiBvbiAqSG9sb25lbWEqIChQbGFjb2Rlcm1pKSBiYXNlZCBvbiBtYXRlcmlhbCBmcm9tIHRoZSBGcmFzbmlhbiBHb2dvIEZvcm1hdGlvbiBhbmQgdGhlIEdpdmV0aWFuLUZyYXNuaWFuIEduZXVkbmEgRm9ybWF0aW9uLCBXZXN0ZXJuIEF1c3RyYWxpYS4gKkdlb2RpdmVyc2l0YXMqICoqMTk5OSoqLCAyMSwgNjktODQuDQoNClRyaW5hanN0aWMsIEsuOyBMb25nLCBKLkEuOyBTYW5jaGV6LCBTLjsgQm9pc3ZlcnQsIEMuQS47IFNuaXR0aW5nLCBELjsgVGFmZm9yZWF1LCBQLjsgRHVwcmV0LCBWLjsgQ2xlbWVudCwgQS5NLjsgQ3VycmllLCBQLkQuOyBSb2Vsb2ZzLCBCLjsgZXQgYWwuIEV4Y2VwdGlvbmFsIHByZXNlcnZhdGlvbiBvZiBvcmdhbnMgaW4gRGV2b25pYW4gcGxhY29kZXJtcyBmcm9tIHRoZSBHb2dvIGxhZ2Vyc3TDpHR0ZS4gKlNjaWVuY2UqICoqMjAyMioqLCAzNzcsIDEzMTEtMTMxNCwgZG9pOjEwLjExMjYvc2NpZW5jZS5hYmYzMjg5DQoNClZhcm9qZWFuLCBELkguIFN5c3RlbWF0aWNzIG9mIHRoZSBnZW51cyAqRWNoaW5vcmhpbnVzKiBCbGFpbnZpbGxlLCBiYXNlZCBvbiBhIHN0dWR5IG9mIHRoZSBwcmlja2x5IHNoYXJrICpFY2hpbm9yaGludXMgY29va2VpKiBQaWV0c2NobWFubi4gRnJlc25vIFN0YXRlIENvbGxlZ2UsIEZyZXNubywgKioxOTcyKiouDQoNClZpbGxhcmlubywgTS5CLiBSYW1hbnVqYW4ncyBQZXJpbWV0ZXIgb2YgYW4gRWxsaXBzZS4gKkFyWGl2IE1hdGgqICoqMjAwOCoqLiBkb2k6aHR0cHM6Ly9kb2kub3JnLzEwLjQ4NTUwL2FyWGl2Lm1hdGgvMDUwNjM4NA0KDQpXYWxsLCBXLlAuIFRoZSBDb3JyZWxhdGlvbiBiZXR3ZWVuIEhpZ2ggTGltYi1Cb25lIERlbnNpdHkgYW5kIEFxdWF0aWMgSGFiaXRzIGluIFJlY2VudCBNYW1tYWxzLiAqSm91cm5hbCBvZiBQYWxlb250b2xvZ3kqICoqMTk4MyoqLCA1NywgMTk3LTIwNy4NCg0KV2VkZWwsIE0uSi4gRXZpZGVuY2UgZm9yIGJpcmQtbGlrZSBhaXIgc2FjcyBpbiBzYXVyaXNjaGlhbiBkaW5vc2F1cnMuICpKb3VybmFsIG9mIEV4cGVyaW1lbnRhbCBab29sb2d5KiAqKjIwMDkqKiwgMzExQSwgNjExLTYyOC4gZG9pOmh0dHBzOi8vZG9pLm9yZy8xMC4xMDAyL2plei41MTMNCjwvZGl2Pg0KDQojIFNlc3Npb24gSW5mb3JtYXRpb24NCg0KYGBge3J9DQp4ZnVuOjpzZXNzaW9uX2luZm8oKQ0KYGBg