Introduction
GROUP BY and PARTITION BY represent two fundamentally different operations in SQL. GROUP BY performs aggregation by collapsing rows into groups, returning one row per group. PARTITION BY, used with window functions, divides rows into partitions but preserves all original rows while adding computed values based on partition-level calculations. Understanding this distinction is essential for writing efficient analytical queries.
Execution Order and Query Processing
SQL queries execute in a specific order that determines how GROUP BY and PARTITION BY interact. The execution sequence proceeds as follows: FROM and JOIN clauses retrieve data, WHERE filters rows, GROUP BY aggregates and collapses rows, HAVING filters groups, SELECT applies window functions including PARTITION BY, ORDER BY sorts results, and finally LIMIT restricts output. This sequence means GROUP BY executes before window functions, so PARTITION BY operates on already-aggregated data when both appear in the same query.
Sample Dataset
To illustrate the use of GROUP BY and PARTITION BY, consider an employee salary dataset:
CREATE TABLE employees (
employee_id INTEGER,
name TEXT,
department TEXT,
salary INTEGER,
hire_date DATE
);
INSERT INTO employees VALUES
(1, 'Kern, Jason', 'Sales', 60000, '2020-01-15'),
(2, 'Samansky, Alison', 'Sales', 65000, '2019-03-20'),
(3, 'McCullum, Charlie', 'Sales', 70000, '2018-07-10'),
(4, 'Kaiser, David', 'Engineering', 80000, '2021-02-01'),
(5, 'da Silva, Evina', 'Engineering', 85000, '2020-06-15'),
(6, 'Olase, Francis', 'Engineering', 90000, '2019-11-30'),
(7, 'Kirti, Gautham', 'HR', 55000, '2021-08-22'),
(8, 'Cheng, Yufan', 'HR', 58000, '2020-12-05');
GROUP BY Behavior
GROUP BY collapses rows based on specified grouping columns and requires aggregate functions for non-grouped columns. The operation fundamentally reduces the result set size.
SELECT
department,
COUNT(*) as employee_count,
AVG(salary) as avg_salary,
MIN(salary) as min_salary,
MAX(salary) as max_salary
FROM employees
GROUP BY department;
Table 1: 3 records
| Engineering |
3 |
85000 |
80000 |
90000 |
| HR |
2 |
56500 |
55000 |
58000 |
| Sales |
3 |
65000 |
60000 |
70000 |
This query returns three rows, one per department, with aggregated statistics. The original eight employee records collapse into three department summaries. Any column in the SELECT clause must either appear in the GROUP BY clause or be wrapped in an aggregate function.
-- Invalid: name is neither grouped nor aggregated
SELECT department, name, AVG(salary)
FROM employees
GROUP BY department;
-- Valid: all non-aggregated columns are in GROUP BY
SELECT department, name, AVG(salary) as avg_salary
FROM employees
GROUP BY department, name;
PARTITION BY Behavior
PARTITION BY divides rows into partitions for window function calculations while preserving all original rows. Window functions compute values based on sets of rows related to the current row. Notice how the PARTITION BY clause is part of the SELECT clause and applied to aggregation functions. Of course, some aggregation results will be the same for all rows, but others can be the result of calculations over partitions.
SELECT
employee_id,
name,
department,
salary,
COUNT(*) OVER (PARTITION BY department) as dept_employee_count,
AVG(salary) OVER (PARTITION BY department) as dept_avg_salary,
salary - AVG(salary) OVER (PARTITION BY department) as diff_from_avg
FROM employees;
Table 2: 8 records
| 4 |
Kaiser, David |
Engineering |
80000 |
3 |
85000 |
-5000 |
| 5 |
da Silva, Evina |
Engineering |
85000 |
3 |
85000 |
0 |
| 6 |
Olase, Francis |
Engineering |
90000 |
3 |
85000 |
5000 |
| 7 |
Kirti, Gautham |
HR |
55000 |
2 |
56500 |
-1500 |
| 8 |
Cheng, Yufan |
HR |
58000 |
2 |
56500 |
1500 |
| 1 |
Kern, Jason |
Sales |
60000 |
3 |
65000 |
-5000 |
| 2 |
Samansky, Alison |
Sales |
65000 |
3 |
65000 |
0 |
| 3 |
McCullum, Charlie |
Sales |
70000 |
3 |
65000 |
5000 |
This query returns all eight rows with department-level statistics added to each row. Every employee in Sales has the same department average of 65000, while every employee in Engineering has 85000. The partition-level calculations are repeated for each row in the partition.
Ranking and Analytical Functions
Window functions support operations impossible with GROUP BY alone. Ranking functions assign sequential numbers to rows within partitions.
SELECT
name,
department,
salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) as salary_rank,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) as rank_with_ties,
DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) as dense_rank
FROM employees;
Table 3: 8 records
| Olase, Francis |
Engineering |
90000 |
1 |
1 |
1 |
| da Silva, Evina |
Engineering |
85000 |
2 |
2 |
2 |
| Kaiser, David |
Engineering |
80000 |
3 |
3 |
3 |
| Cheng, Yufan |
HR |
58000 |
1 |
1 |
1 |
| Kirti, Gautham |
HR |
55000 |
2 |
2 |
2 |
| McCullum, Charlie |
Sales |
70000 |
1 |
1 |
1 |
| Samansky, Alison |
Sales |
65000 |
2 |
2 |
2 |
| Kern, Jason |
Sales |
60000 |
3 |
3 |
3 |
ROW_NUMBER assigns unique sequential integers even when values tie. RANK assigns the same rank to tied values and skips subsequent ranks. DENSE_RANK assigns the same rank to ties without skipping ranks. Within the Sales department, Charlie ranks first at 70000, Bob second at 65000, and Alice third at 60000.
LEAD and LAG functions access values from other rows relative to the current row within a partition.
SELECT
name,
department,
salary,
LAG(salary) OVER (PARTITION BY department ORDER BY salary) as prev_salary,
LEAD(salary) OVER (PARTITION BY department ORDER BY salary) as next_salary,
salary - LAG(salary) OVER (PARTITION BY department ORDER BY salary) as increase
FROM employees
ORDER BY department, salary;
Table 4: 8 records
| Kaiser, David |
Engineering |
80000 |
NA |
85000 |
NA |
| da Silva, Evina |
Engineering |
85000 |
80000 |
90000 |
5000 |
| Olase, Francis |
Engineering |
90000 |
85000 |
NA |
5000 |
| Kirti, Gautham |
HR |
55000 |
NA |
58000 |
NA |
| Cheng, Yufan |
HR |
58000 |
55000 |
NA |
3000 |
| Kern, Jason |
Sales |
60000 |
NA |
65000 |
NA |
| Samansky, Alison |
Sales |
65000 |
60000 |
70000 |
5000 |
| McCullum, Charlie |
Sales |
70000 |
65000 |
NA |
5000 |
Combining GROUP BY and PARTITION BY
Queries can combine both operations by using subqueries or common table expressions. GROUP BY first aggregates data, then PARTITION BY performs window calculations on the aggregated results. We will illustrate this with a different database.
-- Create the table first
CREATE TABLE test_scores (
student_id INTEGER,
student_name TEXT,
class TEXT,
test_name TEXT,
score INTEGER
);
-- Insert sample data
INSERT INTO test_scores VALUES
(1, 'Alice', 'Math', 'Test1', 85),
(1, 'Alice', 'Math', 'Test2', 90),
(1, 'Alice', 'Math', 'Test3', 88),
(2, 'Bob', 'Math', 'Test1', 78),
(2, 'Bob', 'Math', 'Test2', 82),
(2, 'Bob', 'Math', 'Test3', 80),
(3, 'Charlie', 'Science', 'Test1', 92),
(3, 'Charlie', 'Science', 'Test2', 95),
(3, 'Charlie', 'Science', 'Test3', 94),
(4, 'David', 'Science', 'Test1', 88),
(4, 'David', 'Science', 'Test2', 90),
(4, 'David', 'Science', 'Test3', 89);
SELECT
student_name,
class,
avg_score,
RANK() OVER (PARTITION BY class ORDER BY avg_score DESC) as class_rank,
AVG(avg_score) OVER (PARTITION BY class) as class_overall_avg
FROM (
SELECT
student_name,
class,
AVG(score) as avg_score
FROM test_scores
GROUP BY student_name, class
)
ORDER BY class, class_rank;
Table 5: 4 records
| Alice |
Math |
87.66667 |
1 |
83.83333 |
| Bob |
Math |
80.00000 |
2 |
83.83333 |
| Charlie |
Science |
93.66667 |
1 |
91.33333 |
| David |
Science |
89.00000 |
2 |
91.33333 |
-- Calculate student averages, then rank within classes
SELECT
student_name,
class,
avg_score,
RANK() OVER (PARTITION BY class ORDER BY avg_score DESC) as class_rank,
AVG(avg_score) OVER (PARTITION BY class) as class_overall_avg
FROM (
SELECT
student_name,
class,
AVG(score) as avg_score
FROM test_scores
GROUP BY student_name, class
) student_averages
ORDER BY class, class_rank;
Table 6: 4 records
| Alice |
Math |
87.66667 |
1 |
83.83333 |
| Bob |
Math |
80.00000 |
2 |
83.83333 |
| Charlie |
Science |
93.66667 |
1 |
91.33333 |
| David |
Science |
89.00000 |
2 |
91.33333 |
The subquery first collapses multiple test scores per student into a single average using GROUP BY. The outer query then applies window functions to rank students within their classes and compare individual averages to class averages. This pattern is common in analytical queries requiring both aggregation and comparative analysis.
Sales Analysis Example
Consider a sales dataset demonstrating practical applications of combined GROUP BY and PARTITION BY operations.
CREATE TABLE sales (
sale_id INTEGER,
salesperson TEXT,
region TEXT,
sale_amount REAL,
sale_date TEXT
);
INSERT INTO sales VALUES
(1, 'Alice', 'North', 1000, '2024-01-15'),
(2, 'Alice', 'North', 1500, '2024-01-20'),
(3, 'Bob', 'North', 800, '2024-01-18'),
(4, 'Bob', 'North', 1200, '2024-01-22'),
(5, 'Charlie', 'South', 2000, '2024-01-16'),
(6, 'Charlie', 'South', 1800, '2024-01-25'),
(7, 'David', 'South', 1500, '2024-01-17'),
(8, 'David', 'South', 1600, '2024-01-23');
A comprehensive analysis ranks salespeople within regions while calculating regional statistics.
SELECT
salesperson,
region,
total_sales,
RANK() OVER (PARTITION BY region ORDER BY total_sales DESC) as regional_rank,
AVG(total_sales) OVER (PARTITION BY region) as regional_avg,
total_sales - AVG(total_sales) OVER (PARTITION BY region) as diff_from_avg,
ROUND(100.0 * total_sales / SUM(total_sales) OVER (PARTITION BY region), 2) as pct_of_regional_total
FROM (
SELECT
salesperson,
region,
SUM(sale_amount) as total_sales
FROM sales
GROUP BY salesperson, region
) salesperson_totals
ORDER BY region, regional_rank;
Table 7: 4 records
| Alice |
North |
2500 |
1 |
2250 |
250 |
55.56 |
| Bob |
North |
2000 |
2 |
2250 |
-250 |
44.44 |
| Charlie |
South |
3800 |
1 |
3450 |
350 |
55.07 |
| David |
South |
3100 |
2 |
3450 |
-350 |
44.93 |
The inner query aggregates individual sales into per-salesperson totals using GROUP BY. The outer query then ranks salespeople within regions and calculates each salesperson’s percentage of regional total sales. Alice leads the North region with 2500 in total sales, representing 55.56 percent of the regional total, while Bob trails with 2000 representing 44.44 percent.
Running Totals and Window Frames
Window functions support frame specifications for calculating running aggregates. The frame defines which rows relative to the current row participate in the calculation.
SELECT
name,
department,
salary,
hire_date,
SUM(salary) OVER (
PARTITION BY department
ORDER BY hire_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) as cumulative_dept_salary
FROM employees
ORDER BY department, hire_date;
Table 8: 8 records
| Olase, Francis |
Engineering |
90000 |
2019-11-30 |
90000 |
| da Silva, Evina |
Engineering |
85000 |
2020-06-15 |
175000 |
| Kaiser, David |
Engineering |
80000 |
2021-02-01 |
255000 |
| Cheng, Yufan |
HR |
58000 |
2020-12-05 |
58000 |
| Kirti, Gautham |
HR |
55000 |
2021-08-22 |
113000 |
| McCullum, Charlie |
Sales |
70000 |
2018-07-10 |
70000 |
| Samansky, Alison |
Sales |
65000 |
2019-03-20 |
135000 |
| Kern, Jason |
Sales |
60000 |
2020-01-15 |
195000 |
This query calculates a running total of salaries within each department ordered by hire date. For each employee, the running total includes all employees hired in the same department up to and including the current employee’s hire date. The frame clause “ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW” specifies this behavior explicitly.
Decision Framework
Choose GROUP BY when creating summary reports that require one row per group, when reducing result sets through aggregation, or when building dashboards with aggregate statistics. The operation is appropriate when detail-level data is unnecessary in the output.
Choose PARTITION BY when preserving detail rows while adding aggregate context, when ranking rows within groups, when comparing individual values to group statistics, or when calculating running totals and moving averages. Window functions excel at analytical queries requiring both detail and summary information simultaneously.
Combine both operations when first aggregating data then performing comparative analysis on the aggregates, when ranking aggregated groups, or when calculating percentages of group totals from already-aggregated data.
Common Errors
Attempting to select non-aggregated columns without including them in GROUP BY causes errors. Every column in the SELECT clause must either appear in GROUP BY or be wrapped in an aggregate function.
-- Error: name must be grouped or aggregated
SELECT department, name, AVG(salary)
FROM employees
GROUP BY department;
-- Correct: include name in GROUP BY
SELECT department, name, AVG(salary)
FROM employees
GROUP BY department, name;
When combining GROUP BY and PARTITION BY, ensure partition columns exist in the grouped result set. If GROUP BY collapses away a column, that column cannot be used in PARTITION BY.
-- Error: employee_id grouped away but needed for partition
SELECT department, SUM(salary),
RANK() OVER (PARTITION BY employee_id ORDER BY salary)
FROM employees GROUP BY department;
-- Correct: partition by what remains after grouping
SELECT department, SUM(salary) as total_salary,
RANK() OVER (ORDER BY SUM(salary) DESC) as dept_rank
FROM employees GROUP BY department;
Expecting PARTITION BY to reduce row counts is a common misconception. Window functions always preserve the input row count unless DISTINCT or additional filtering is applied.
Notes
SQLite added comprehensive window function support including PARTITION BY in version 3.25.0 released in September 2018. Any modern SQLite installation supports these features. Check the version with SELECT sqlite_version(); to confirm compatibility. Versions prior to 3.25.0 require complex workarounds using self-joins and subqueries that are both inefficient and difficult to maintain.
Acknowledgements
Claude 4.6 (Sonnet) was used in the preparation of this lesson.
LS0tCnRpdGxlOiAiR1JPVVAgQlkgYW5kIFBBUlRJVElPTiBCWTogVHdvIEFwcHJvYWNoZXMgdG8gQW5hbHl0aWNhbCBRdWVyaWVzIGluIFNRTCIKcGFyYW1zOgogIGNhdGVnb3J5OiA3MAogIG51bWJlcjogMTE1CiAgdGltZTogNDUKICBsZXZlbDogYmVnaW5uZXIKICB0YWdzOiAic3FsLGdyb3VwIGJ5LHBhcnRpdGlvbixhbmFseXRpY3MiCiAgZGVzY3JpcHRpb246ICJFeGFtaW5lcyB0aGUgZnVuZGFtZW50YWwgZGlmZmVyZW5jZXMgYmV0d2VlbiBHUk9VUCBCWSAKICAgICAgICAgICAgICAgIGFnZ3JlZ2F0aW9uLCB3aGljaCBjb2xsYXBzZXMgcm93cyBpbnRvIHN1bW1hcnkgZ3JvdXBzLCAKICAgICAgICAgICAgICAgIGFuZCBQQVJUSVRJT04gQlkgd2luZG93IGZ1bmN0aW9ucywgd2hpY2ggcHJlc2VydmUgYWxsIHJvd3MgCiAgICAgICAgICAgICAgICB3aGlsZSBhZGRpbmcgcGFydGl0aW9uLWxldmVsIGNhbGN1bGF0aW9ucy4gQ29tYmluZSBib3RoIAogICAgICAgICAgICAgICAgb3BlcmF0aW9ucyBmb3IgY29tcGxleCBhbmFseXRpY2FsIHF1ZXJpZXMuIFByb3ZpZGVzCiAgICAgICAgICAgICAgICBleGFtcGxlcyB1c2luZyBTUUxpdGUgYW5kIFIgYnV0IGxlc3NvbiBpcyBhcHBsaWNhYmxlIHRvCiAgICAgICAgICAgICAgICBhbnkgZGF0YWJhc2UgYW5kIHByb2dyYW1taW5nIGxhbmd1YWdlLiIKZGF0ZTogIjxzbWFsbD5gciBTeXMuRGF0ZSgpYDwvc21hbGw+IgphdXRob3I6ICI8c21hbGw+TWFydGluIFNjaGVkbGJhdWVyPC9zbWFsbD4iCmVtYWlsOiAibS5zY2hlZGxiYXVlckBuZXUuZWR1IgphZmZpbGl0YXRpb246ICJOb3J0aGVhc3Rlcm4gVW5pdmVyc2l0eSIKb3V0cHV0OiAKICBib29rZG93bjo6aHRtbF9kb2N1bWVudDI6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29sbGFwc2VkOiBmYWxzZQogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgdGhlbWU6IGx1bWVuCiAgICBoaWdobGlnaHQ6IHRhbmdvCi0tLQoKLS0tCnRpdGxlOiAiPHNtYWxsPmByIHBhcmFtcyRjYXRlZ29yeWAuYHIgcGFyYW1zJG51bWJlcmA8L3NtYWxsPjxici8+PHNwYW4gc3R5bGU9J2NvbG9yOiAjMkU0MDUzOyBmb250LXNpemU6IDAuOWVtJz5gciBybWFya2Rvd246Om1ldGFkYXRhJHRpdGxlYDwvc3Bhbj4iCi0tLQoKYGBge3IgZWNobz1GfQojIFBhY2thZ2UgbmFtZXMKcGFja2FnZXMgPC0gYygiaGVyZSIsICJSU1FMaXRlIikKCiMgSW5zdGFsbCBwYWNrYWdlcyBub3QgeWV0IGluc3RhbGxlZAppbnN0YWxsZWRfcGFja2FnZXMgPC0gcGFja2FnZXMgJWluJSByb3duYW1lcyhpbnN0YWxsZWQucGFja2FnZXMoKSkKaWYgKGFueShpbnN0YWxsZWRfcGFja2FnZXMgPT0gRkFMU0UpKSB7CiAgaW5zdGFsbC5wYWNrYWdlcyhwYWNrYWdlc1shaW5zdGFsbGVkX3BhY2thZ2VzXSwgcmVwb3MgPSAiaHR0cHM6Ly9jbG91ZC5yLXByb2plY3Qub3JnIikKfQoKIyBQYWNrYWdlcyBsb2FkaW5nCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhpbnZpc2libGUobGFwcGx5KHBhY2thZ2VzLCBsaWJyYXJ5LCBjaGFyYWN0ZXIub25seSA9IFRSVUUpKSkKYGBgCgpgYGB7ciBjb2RlPXhmdW46OnJlYWRfdXRmOChwYXN0ZTAoaGVyZTo6aGVyZSgpLCcvUi9faW5zZXJ0MkRCLlInKSksIGluY2x1ZGUgPSBGQUxTRX0KYGBgCgojIyBJbnRyb2R1Y3Rpb24KCkdST1VQIEJZIGFuZCBQQVJUSVRJT04gQlkgcmVwcmVzZW50IHR3byBmdW5kYW1lbnRhbGx5IGRpZmZlcmVudCBvcGVyYXRpb25zIGluIFNRTC4gR1JPVVAgQlkgcGVyZm9ybXMgYWdncmVnYXRpb24gYnkgY29sbGFwc2luZyByb3dzIGludG8gZ3JvdXBzLCByZXR1cm5pbmcgb25lIHJvdyBwZXIgZ3JvdXAuIFBBUlRJVElPTiBCWSwgdXNlZCB3aXRoIHdpbmRvdyBmdW5jdGlvbnMsIGRpdmlkZXMgcm93cyBpbnRvIHBhcnRpdGlvbnMgYnV0IHByZXNlcnZlcyBhbGwgb3JpZ2luYWwgcm93cyB3aGlsZSBhZGRpbmcgY29tcHV0ZWQgdmFsdWVzIGJhc2VkIG9uIHBhcnRpdGlvbi1sZXZlbCBjYWxjdWxhdGlvbnMuIFVuZGVyc3RhbmRpbmcgdGhpcyBkaXN0aW5jdGlvbiBpcyBlc3NlbnRpYWwgZm9yIHdyaXRpbmcgZWZmaWNpZW50IGFuYWx5dGljYWwgcXVlcmllcy4KCiMjIEV4ZWN1dGlvbiBPcmRlciBhbmQgUXVlcnkgUHJvY2Vzc2luZwoKU1FMIHF1ZXJpZXMgZXhlY3V0ZSBpbiBhIHNwZWNpZmljIG9yZGVyIHRoYXQgZGV0ZXJtaW5lcyBob3cgR1JPVVAgQlkgYW5kIFBBUlRJVElPTiBCWSBpbnRlcmFjdC4gVGhlIGV4ZWN1dGlvbiBzZXF1ZW5jZSBwcm9jZWVkcyBhcyBmb2xsb3dzOiBGUk9NIGFuZCBKT0lOIGNsYXVzZXMgcmV0cmlldmUgZGF0YSwgV0hFUkUgZmlsdGVycyByb3dzLCBHUk9VUCBCWSBhZ2dyZWdhdGVzIGFuZCBjb2xsYXBzZXMgcm93cywgSEFWSU5HIGZpbHRlcnMgZ3JvdXBzLCBTRUxFQ1QgYXBwbGllcyB3aW5kb3cgZnVuY3Rpb25zIGluY2x1ZGluZyBQQVJUSVRJT04gQlksIE9SREVSIEJZIHNvcnRzIHJlc3VsdHMsIGFuZCBmaW5hbGx5IExJTUlUIHJlc3RyaWN0cyBvdXRwdXQuIFRoaXMgc2VxdWVuY2UgbWVhbnMgR1JPVVAgQlkgZXhlY3V0ZXMgYmVmb3JlIHdpbmRvdyBmdW5jdGlvbnMsIHNvIFBBUlRJVElPTiBCWSBvcGVyYXRlcyBvbiBhbHJlYWR5LWFnZ3JlZ2F0ZWQgZGF0YSB3aGVuIGJvdGggYXBwZWFyIGluIHRoZSBzYW1lIHF1ZXJ5LgoKIyMgU2FtcGxlIERhdGFzZXQKClRvIGlsbHVzdHJhdGUgdGhlIHVzZSBvZiBHUk9VUCBCWSBhbmQgUEFSVElUSU9OIEJZLCBjb25zaWRlciBhbiBlbXBsb3llZSBzYWxhcnkgZGF0YXNldDoKCmBgYHtyIGVjaG89Riwgd2FybmluZz1GQUxTRX0KbGlicmFyeShSU1FMaXRlKQpkYmNvbiA8LSBkYkNvbm5lY3QoUlNRTGl0ZTo6U1FMaXRlKCksICI6bWVtb3J5OiIpCmBgYAoKYGBge3NxbCBjb25uZWN0aW9uPWRiY29ufQpDUkVBVEUgVEFCTEUgZW1wbG95ZWVzICgKICAgIGVtcGxveWVlX2lkIElOVEVHRVIsCiAgICBuYW1lIFRFWFQsCiAgICBkZXBhcnRtZW50IFRFWFQsCiAgICBzYWxhcnkgSU5URUdFUiwKICAgIGhpcmVfZGF0ZSBEQVRFCik7CmBgYAoKYGBge3NxbCBjb25uZWN0aW9uPWRiY29ufQpJTlNFUlQgSU5UTyBlbXBsb3llZXMgVkFMVUVTCigxLCAnS2VybiwgSmFzb24nLCAnU2FsZXMnLCA2MDAwMCwgJzIwMjAtMDEtMTUnKSwKKDIsICdTYW1hbnNreSwgQWxpc29uJywgJ1NhbGVzJywgNjUwMDAsICcyMDE5LTAzLTIwJyksCigzLCAnTWNDdWxsdW0sIENoYXJsaWUnLCAnU2FsZXMnLCA3MDAwMCwgJzIwMTgtMDctMTAnKSwKKDQsICdLYWlzZXIsIERhdmlkJywgJ0VuZ2luZWVyaW5nJywgODAwMDAsICcyMDIxLTAyLTAxJyksCig1LCAnZGEgU2lsdmEsIEV2aW5hJywgJ0VuZ2luZWVyaW5nJywgODUwMDAsICcyMDIwLTA2LTE1JyksCig2LCAnT2xhc2UsIEZyYW5jaXMnLCAnRW5naW5lZXJpbmcnLCA5MDAwMCwgJzIwMTktMTEtMzAnKSwKKDcsICdLaXJ0aSwgR2F1dGhhbScsICdIUicsIDU1MDAwLCAnMjAyMS0wOC0yMicpLAooOCwgJ0NoZW5nLCBZdWZhbicsICdIUicsIDU4MDAwLCAnMjAyMC0xMi0wNScpOwpgYGAKCiMjIEdST1VQIEJZIEJlaGF2aW9yCgpHUk9VUCBCWSBjb2xsYXBzZXMgcm93cyBiYXNlZCBvbiBzcGVjaWZpZWQgZ3JvdXBpbmcgY29sdW1ucyBhbmQgcmVxdWlyZXMgYWdncmVnYXRlIGZ1bmN0aW9ucyBmb3Igbm9uLWdyb3VwZWQgY29sdW1ucy4gVGhlIG9wZXJhdGlvbiBmdW5kYW1lbnRhbGx5IHJlZHVjZXMgdGhlIHJlc3VsdCBzZXQgc2l6ZS4KCmBgYHtzcWwgY29ubmVjdGlvbj1kYmNvbn0KU0VMRUNUIAogICAgZGVwYXJ0bWVudCwKICAgIENPVU5UKCopIGFzIGVtcGxveWVlX2NvdW50LAogICAgQVZHKHNhbGFyeSkgYXMgYXZnX3NhbGFyeSwKICAgIE1JTihzYWxhcnkpIGFzIG1pbl9zYWxhcnksCiAgICBNQVgoc2FsYXJ5KSBhcyBtYXhfc2FsYXJ5CkZST00gZW1wbG95ZWVzCkdST1VQIEJZIGRlcGFydG1lbnQ7CmBgYAoKVGhpcyBxdWVyeSByZXR1cm5zIHRocmVlIHJvd3MsIG9uZSBwZXIgZGVwYXJ0bWVudCwgd2l0aCBhZ2dyZWdhdGVkIHN0YXRpc3RpY3MuIFRoZSBvcmlnaW5hbCBlaWdodCBlbXBsb3llZSByZWNvcmRzIGNvbGxhcHNlIGludG8gdGhyZWUgZGVwYXJ0bWVudCBzdW1tYXJpZXMuIEFueSBjb2x1bW4gaW4gdGhlIFNFTEVDVCBjbGF1c2UgbXVzdCBlaXRoZXIgYXBwZWFyIGluIHRoZSBHUk9VUCBCWSBjbGF1c2Ugb3IgYmUgd3JhcHBlZCBpbiBhbiBhZ2dyZWdhdGUgZnVuY3Rpb24uCgpgYGAgc3FsCi0tIEludmFsaWQ6IG5hbWUgaXMgbmVpdGhlciBncm91cGVkIG5vciBhZ2dyZWdhdGVkClNFTEVDVCBkZXBhcnRtZW50LCBuYW1lLCBBVkcoc2FsYXJ5KSAKICBGUk9NIGVtcGxveWVlcyAKIEdST1VQIEJZIGRlcGFydG1lbnQ7CgotLSBWYWxpZDogYWxsIG5vbi1hZ2dyZWdhdGVkIGNvbHVtbnMgYXJlIGluIEdST1VQIEJZClNFTEVDVCBkZXBhcnRtZW50LCBuYW1lLCBBVkcoc2FsYXJ5KSBhcyBhdmdfc2FsYXJ5CiAgRlJPTSBlbXBsb3llZXMKIEdST1VQIEJZIGRlcGFydG1lbnQsIG5hbWU7CmBgYAoKIyMgUEFSVElUSU9OIEJZIEJlaGF2aW9yCgpQQVJUSVRJT04gQlkgZGl2aWRlcyByb3dzIGludG8gcGFydGl0aW9ucyBmb3Igd2luZG93IGZ1bmN0aW9uIGNhbGN1bGF0aW9ucyB3aGlsZSBwcmVzZXJ2aW5nIGFsbCBvcmlnaW5hbCByb3dzLiBXaW5kb3cgZnVuY3Rpb25zIGNvbXB1dGUgdmFsdWVzIGJhc2VkIG9uIHNldHMgb2Ygcm93cyByZWxhdGVkIHRvIHRoZSBjdXJyZW50IHJvdy4gTm90aWNlIGhvdyB0aGUgUEFSVElUSU9OIEJZIGNsYXVzZSBpcyBwYXJ0IG9mIHRoZSBTRUxFQ1QgY2xhdXNlIGFuZCBhcHBsaWVkIHRvIGFnZ3JlZ2F0aW9uIGZ1bmN0aW9ucy4gT2YgY291cnNlLCBzb21lIGFnZ3JlZ2F0aW9uIHJlc3VsdHMgd2lsbCBiZSB0aGUgc2FtZSBmb3IgYWxsIHJvd3MsIGJ1dCBvdGhlcnMgY2FuIGJlIHRoZSByZXN1bHQgb2YgY2FsY3VsYXRpb25zIG92ZXIgcGFydGl0aW9ucy4KCmBgYHtzcWwgY29ubmVjdGlvbj1kYmNvbn0KU0VMRUNUIAogICAgZW1wbG95ZWVfaWQsCiAgICBuYW1lLAogICAgZGVwYXJ0bWVudCwKICAgIHNhbGFyeSwKICAgIENPVU5UKCopIE9WRVIgKFBBUlRJVElPTiBCWSBkZXBhcnRtZW50KSBhcyBkZXB0X2VtcGxveWVlX2NvdW50LAogICAgQVZHKHNhbGFyeSkgT1ZFUiAoUEFSVElUSU9OIEJZIGRlcGFydG1lbnQpIGFzIGRlcHRfYXZnX3NhbGFyeSwKICAgIHNhbGFyeSAtIEFWRyhzYWxhcnkpIE9WRVIgKFBBUlRJVElPTiBCWSBkZXBhcnRtZW50KSBhcyBkaWZmX2Zyb21fYXZnCkZST00gZW1wbG95ZWVzOwpgYGAKClRoaXMgcXVlcnkgcmV0dXJucyBhbGwgZWlnaHQgcm93cyB3aXRoIGRlcGFydG1lbnQtbGV2ZWwgc3RhdGlzdGljcyBhZGRlZCB0byBlYWNoIHJvdy4gRXZlcnkgZW1wbG95ZWUgaW4gU2FsZXMgaGFzIHRoZSBzYW1lIGRlcGFydG1lbnQgYXZlcmFnZSBvZiA2NTAwMCwgd2hpbGUgZXZlcnkgZW1wbG95ZWUgaW4gRW5naW5lZXJpbmcgaGFzIDg1MDAwLiBUaGUgcGFydGl0aW9uLWxldmVsIGNhbGN1bGF0aW9ucyBhcmUgcmVwZWF0ZWQgZm9yIGVhY2ggcm93IGluIHRoZSBwYXJ0aXRpb24uCgojIyBSYW5raW5nIGFuZCBBbmFseXRpY2FsIEZ1bmN0aW9ucwoKV2luZG93IGZ1bmN0aW9ucyBzdXBwb3J0IG9wZXJhdGlvbnMgaW1wb3NzaWJsZSB3aXRoIEdST1VQIEJZIGFsb25lLiBSYW5raW5nIGZ1bmN0aW9ucyBhc3NpZ24gc2VxdWVudGlhbCBudW1iZXJzIHRvIHJvd3Mgd2l0aGluIHBhcnRpdGlvbnMuCgpgYGB7c3FsIGNvbm5lY3Rpb249ZGJjb259ClNFTEVDVCAKICAgIG5hbWUsCiAgICBkZXBhcnRtZW50LAogICAgc2FsYXJ5LAogICAgUk9XX05VTUJFUigpIE9WRVIgKFBBUlRJVElPTiBCWSBkZXBhcnRtZW50IE9SREVSIEJZIHNhbGFyeSBERVNDKSBhcyBzYWxhcnlfcmFuaywKICAgIFJBTksoKSBPVkVSIChQQVJUSVRJT04gQlkgZGVwYXJ0bWVudCBPUkRFUiBCWSBzYWxhcnkgREVTQykgYXMgcmFua193aXRoX3RpZXMsCiAgICBERU5TRV9SQU5LKCkgT1ZFUiAoUEFSVElUSU9OIEJZIGRlcGFydG1lbnQgT1JERVIgQlkgc2FsYXJ5IERFU0MpIGFzIGRlbnNlX3JhbmsKRlJPTSBlbXBsb3llZXM7CmBgYAoKUk9XX05VTUJFUiBhc3NpZ25zIHVuaXF1ZSBzZXF1ZW50aWFsIGludGVnZXJzIGV2ZW4gd2hlbiB2YWx1ZXMgdGllLiBSQU5LIGFzc2lnbnMgdGhlIHNhbWUgcmFuayB0byB0aWVkIHZhbHVlcyBhbmQgc2tpcHMgc3Vic2VxdWVudCByYW5rcy4gREVOU0VfUkFOSyBhc3NpZ25zIHRoZSBzYW1lIHJhbmsgdG8gdGllcyB3aXRob3V0IHNraXBwaW5nIHJhbmtzLiBXaXRoaW4gdGhlIFNhbGVzIGRlcGFydG1lbnQsIENoYXJsaWUgcmFua3MgZmlyc3QgYXQgNzAwMDAsIEJvYiBzZWNvbmQgYXQgNjUwMDAsIGFuZCBBbGljZSB0aGlyZCBhdCA2MDAwMC4KCkxFQUQgYW5kIExBRyBmdW5jdGlvbnMgYWNjZXNzIHZhbHVlcyBmcm9tIG90aGVyIHJvd3MgcmVsYXRpdmUgdG8gdGhlIGN1cnJlbnQgcm93IHdpdGhpbiBhIHBhcnRpdGlvbi4KCmBgYHtzcWwgY29ubmVjdGlvbj1kYmNvbn0KU0VMRUNUIAogICAgbmFtZSwKICAgIGRlcGFydG1lbnQsCiAgICBzYWxhcnksCiAgICBMQUcoc2FsYXJ5KSBPVkVSIChQQVJUSVRJT04gQlkgZGVwYXJ0bWVudCBPUkRFUiBCWSBzYWxhcnkpIGFzIHByZXZfc2FsYXJ5LAogICAgTEVBRChzYWxhcnkpIE9WRVIgKFBBUlRJVElPTiBCWSBkZXBhcnRtZW50IE9SREVSIEJZIHNhbGFyeSkgYXMgbmV4dF9zYWxhcnksCiAgICBzYWxhcnkgLSBMQUcoc2FsYXJ5KSBPVkVSIChQQVJUSVRJT04gQlkgZGVwYXJ0bWVudCBPUkRFUiBCWSBzYWxhcnkpIGFzIGluY3JlYXNlCkZST00gZW1wbG95ZWVzCk9SREVSIEJZIGRlcGFydG1lbnQsIHNhbGFyeTsKYGBgCgojIyBDb21iaW5pbmcgR1JPVVAgQlkgYW5kIFBBUlRJVElPTiBCWQoKUXVlcmllcyBjYW4gY29tYmluZSBib3RoIG9wZXJhdGlvbnMgYnkgdXNpbmcgc3VicXVlcmllcyBvciBjb21tb24gdGFibGUgZXhwcmVzc2lvbnMuIEdST1VQIEJZIGZpcnN0IGFnZ3JlZ2F0ZXMgZGF0YSwgdGhlbiBQQVJUSVRJT04gQlkgcGVyZm9ybXMgd2luZG93IGNhbGN1bGF0aW9ucyBvbiB0aGUgYWdncmVnYXRlZCByZXN1bHRzLiBXZSB3aWxsIGlsbHVzdHJhdGUgdGhpcyB3aXRoIGEgZGlmZmVyZW50IGRhdGFiYXNlLgoKYGBge3NxbCBjb25uZWN0aW9uPWRiY29ufQotLSBDcmVhdGUgdGhlIHRhYmxlIGZpcnN0CkNSRUFURSBUQUJMRSB0ZXN0X3Njb3JlcyAoCiAgICBzdHVkZW50X2lkIElOVEVHRVIsCiAgICBzdHVkZW50X25hbWUgVEVYVCwKICAgIGNsYXNzIFRFWFQsCiAgICB0ZXN0X25hbWUgVEVYVCwKICAgIHNjb3JlIElOVEVHRVIKKTsKYGBgCgpgYGB7c3FsIGNvbm5lY3Rpb249ZGJjb259Ci0tIEluc2VydCBzYW1wbGUgZGF0YQpJTlNFUlQgSU5UTyB0ZXN0X3Njb3JlcyBWQUxVRVMKKDEsICdBbGljZScsICdNYXRoJywgJ1Rlc3QxJywgODUpLAooMSwgJ0FsaWNlJywgJ01hdGgnLCAnVGVzdDInLCA5MCksCigxLCAnQWxpY2UnLCAnTWF0aCcsICdUZXN0MycsIDg4KSwKKDIsICdCb2InLCAnTWF0aCcsICdUZXN0MScsIDc4KSwKKDIsICdCb2InLCAnTWF0aCcsICdUZXN0MicsIDgyKSwKKDIsICdCb2InLCAnTWF0aCcsICdUZXN0MycsIDgwKSwKKDMsICdDaGFybGllJywgJ1NjaWVuY2UnLCAnVGVzdDEnLCA5MiksCigzLCAnQ2hhcmxpZScsICdTY2llbmNlJywgJ1Rlc3QyJywgOTUpLAooMywgJ0NoYXJsaWUnLCAnU2NpZW5jZScsICdUZXN0MycsIDk0KSwKKDQsICdEYXZpZCcsICdTY2llbmNlJywgJ1Rlc3QxJywgODgpLAooNCwgJ0RhdmlkJywgJ1NjaWVuY2UnLCAnVGVzdDInLCA5MCksCig0LCAnRGF2aWQnLCAnU2NpZW5jZScsICdUZXN0MycsIDg5KTsKYGBgCgpgYGB7c3FsIGNvbm5lY3Rpb249ZGJjb259ClNFTEVDVCAKICAgIHN0dWRlbnRfbmFtZSwKICAgIGNsYXNzLAogICAgYXZnX3Njb3JlLAogICAgUkFOSygpIE9WRVIgKFBBUlRJVElPTiBCWSBjbGFzcyBPUkRFUiBCWSBhdmdfc2NvcmUgREVTQykgYXMgY2xhc3NfcmFuaywKICAgIEFWRyhhdmdfc2NvcmUpIE9WRVIgKFBBUlRJVElPTiBCWSBjbGFzcykgYXMgY2xhc3Nfb3ZlcmFsbF9hdmcKRlJPTSAoCiAgICBTRUxFQ1QgCiAgICAgICAgc3R1ZGVudF9uYW1lLAogICAgICAgIGNsYXNzLAogICAgICAgIEFWRyhzY29yZSkgYXMgYXZnX3Njb3JlCiAgICBGUk9NIHRlc3Rfc2NvcmVzCiAgICBHUk9VUCBCWSBzdHVkZW50X25hbWUsIGNsYXNzCikKT1JERVIgQlkgY2xhc3MsIGNsYXNzX3Jhbms7CmBgYAoKYGBge3NxbCBjb25uZWN0aW9uPWRiY29ufQotLSBDYWxjdWxhdGUgc3R1ZGVudCBhdmVyYWdlcywgdGhlbiByYW5rIHdpdGhpbiBjbGFzc2VzClNFTEVDVCAKICAgIHN0dWRlbnRfbmFtZSwKICAgIGNsYXNzLAogICAgYXZnX3Njb3JlLAogICAgUkFOSygpIE9WRVIgKFBBUlRJVElPTiBCWSBjbGFzcyBPUkRFUiBCWSBhdmdfc2NvcmUgREVTQykgYXMgY2xhc3NfcmFuaywKICAgIEFWRyhhdmdfc2NvcmUpIE9WRVIgKFBBUlRJVElPTiBCWSBjbGFzcykgYXMgY2xhc3Nfb3ZlcmFsbF9hdmcKRlJPTSAoCiAgICBTRUxFQ1QgCiAgICAgICAgc3R1ZGVudF9uYW1lLAogICAgICAgIGNsYXNzLAogICAgICAgIEFWRyhzY29yZSkgYXMgYXZnX3Njb3JlCiAgICBGUk9NIHRlc3Rfc2NvcmVzCiAgICBHUk9VUCBCWSBzdHVkZW50X25hbWUsIGNsYXNzCikgc3R1ZGVudF9hdmVyYWdlcwpPUkRFUiBCWSBjbGFzcywgY2xhc3NfcmFuazsKYGBgCgpUaGUgc3VicXVlcnkgZmlyc3QgY29sbGFwc2VzIG11bHRpcGxlIHRlc3Qgc2NvcmVzIHBlciBzdHVkZW50IGludG8gYSBzaW5nbGUgYXZlcmFnZSB1c2luZyBHUk9VUCBCWS4gVGhlIG91dGVyIHF1ZXJ5IHRoZW4gYXBwbGllcyB3aW5kb3cgZnVuY3Rpb25zIHRvIHJhbmsgc3R1ZGVudHMgd2l0aGluIHRoZWlyIGNsYXNzZXMgYW5kIGNvbXBhcmUgaW5kaXZpZHVhbCBhdmVyYWdlcyB0byBjbGFzcyBhdmVyYWdlcy4gVGhpcyBwYXR0ZXJuIGlzIGNvbW1vbiBpbiBhbmFseXRpY2FsIHF1ZXJpZXMgcmVxdWlyaW5nIGJvdGggYWdncmVnYXRpb24gYW5kIGNvbXBhcmF0aXZlIGFuYWx5c2lzLgoKIyMgU2FsZXMgQW5hbHlzaXMgRXhhbXBsZQoKQ29uc2lkZXIgYSBzYWxlcyBkYXRhc2V0IGRlbW9uc3RyYXRpbmcgcHJhY3RpY2FsIGFwcGxpY2F0aW9ucyBvZiBjb21iaW5lZCBHUk9VUCBCWSBhbmQgUEFSVElUSU9OIEJZIG9wZXJhdGlvbnMuCgpgYGB7c3FsIGNvbm5lY3Rpb249ZGJjb259CkNSRUFURSBUQUJMRSBzYWxlcyAoCiAgICBzYWxlX2lkIElOVEVHRVIsCiAgICBzYWxlc3BlcnNvbiBURVhULAogICAgcmVnaW9uIFRFWFQsCiAgICBzYWxlX2Ftb3VudCBSRUFMLAogICAgc2FsZV9kYXRlIFRFWFQKKTsKYGBgCgpgYGB7c3FsIGNvbm5lY3Rpb249ZGJjb259CklOU0VSVCBJTlRPIHNhbGVzIFZBTFVFUwooMSwgJ0FsaWNlJywgJ05vcnRoJywgMTAwMCwgJzIwMjQtMDEtMTUnKSwKKDIsICdBbGljZScsICdOb3J0aCcsIDE1MDAsICcyMDI0LTAxLTIwJyksCigzLCAnQm9iJywgJ05vcnRoJywgODAwLCAnMjAyNC0wMS0xOCcpLAooNCwgJ0JvYicsICdOb3J0aCcsIDEyMDAsICcyMDI0LTAxLTIyJyksCig1LCAnQ2hhcmxpZScsICdTb3V0aCcsIDIwMDAsICcyMDI0LTAxLTE2JyksCig2LCAnQ2hhcmxpZScsICdTb3V0aCcsIDE4MDAsICcyMDI0LTAxLTI1JyksCig3LCAnRGF2aWQnLCAnU291dGgnLCAxNTAwLCAnMjAyNC0wMS0xNycpLAooOCwgJ0RhdmlkJywgJ1NvdXRoJywgMTYwMCwgJzIwMjQtMDEtMjMnKTsKYGBgCgpBIGNvbXByZWhlbnNpdmUgYW5hbHlzaXMgcmFua3Mgc2FsZXNwZW9wbGUgd2l0aGluIHJlZ2lvbnMgd2hpbGUgY2FsY3VsYXRpbmcgcmVnaW9uYWwgc3RhdGlzdGljcy4KCmBgYHtzcWwgY29ubmVjdGlvbj1kYmNvbn0KU0VMRUNUIAogICAgc2FsZXNwZXJzb24sCiAgICByZWdpb24sCiAgICB0b3RhbF9zYWxlcywKICAgIFJBTksoKSBPVkVSIChQQVJUSVRJT04gQlkgcmVnaW9uIE9SREVSIEJZIHRvdGFsX3NhbGVzIERFU0MpIGFzIHJlZ2lvbmFsX3JhbmssCiAgICBBVkcodG90YWxfc2FsZXMpIE9WRVIgKFBBUlRJVElPTiBCWSByZWdpb24pIGFzIHJlZ2lvbmFsX2F2ZywKICAgIHRvdGFsX3NhbGVzIC0gQVZHKHRvdGFsX3NhbGVzKSBPVkVSIChQQVJUSVRJT04gQlkgcmVnaW9uKSBhcyBkaWZmX2Zyb21fYXZnLAogICAgUk9VTkQoMTAwLjAgKiB0b3RhbF9zYWxlcyAvIFNVTSh0b3RhbF9zYWxlcykgT1ZFUiAoUEFSVElUSU9OIEJZIHJlZ2lvbiksIDIpIGFzIHBjdF9vZl9yZWdpb25hbF90b3RhbApGUk9NICgKICAgIFNFTEVDVCAKICAgICAgICBzYWxlc3BlcnNvbiwKICAgICAgICByZWdpb24sCiAgICAgICAgU1VNKHNhbGVfYW1vdW50KSBhcyB0b3RhbF9zYWxlcwogICAgRlJPTSBzYWxlcwogICAgR1JPVVAgQlkgc2FsZXNwZXJzb24sIHJlZ2lvbgopIHNhbGVzcGVyc29uX3RvdGFscwpPUkRFUiBCWSByZWdpb24sIHJlZ2lvbmFsX3Jhbms7CmBgYAoKVGhlIGlubmVyIHF1ZXJ5IGFnZ3JlZ2F0ZXMgaW5kaXZpZHVhbCBzYWxlcyBpbnRvIHBlci1zYWxlc3BlcnNvbiB0b3RhbHMgdXNpbmcgR1JPVVAgQlkuIFRoZSBvdXRlciBxdWVyeSB0aGVuIHJhbmtzIHNhbGVzcGVvcGxlIHdpdGhpbiByZWdpb25zIGFuZCBjYWxjdWxhdGVzIGVhY2ggc2FsZXNwZXJzb24ncyBwZXJjZW50YWdlIG9mIHJlZ2lvbmFsIHRvdGFsIHNhbGVzLiBBbGljZSBsZWFkcyB0aGUgTm9ydGggcmVnaW9uIHdpdGggMjUwMCBpbiB0b3RhbCBzYWxlcywgcmVwcmVzZW50aW5nIDU1LjU2IHBlcmNlbnQgb2YgdGhlIHJlZ2lvbmFsIHRvdGFsLCB3aGlsZSBCb2IgdHJhaWxzIHdpdGggMjAwMCByZXByZXNlbnRpbmcgNDQuNDQgcGVyY2VudC4KCiMjIFJ1bm5pbmcgVG90YWxzIGFuZCBXaW5kb3cgRnJhbWVzCgpXaW5kb3cgZnVuY3Rpb25zIHN1cHBvcnQgZnJhbWUgc3BlY2lmaWNhdGlvbnMgZm9yIGNhbGN1bGF0aW5nIHJ1bm5pbmcgYWdncmVnYXRlcy4gVGhlIGZyYW1lIGRlZmluZXMgd2hpY2ggcm93cyByZWxhdGl2ZSB0byB0aGUgY3VycmVudCByb3cgcGFydGljaXBhdGUgaW4gdGhlIGNhbGN1bGF0aW9uLgoKYGBge3NxbCBjb25uZWN0aW9uPWRiY29ufQpTRUxFQ1QgCiAgICBuYW1lLAogICAgZGVwYXJ0bWVudCwKICAgIHNhbGFyeSwKICAgIGhpcmVfZGF0ZSwKICAgIFNVTShzYWxhcnkpIE9WRVIgKAogICAgICAgIFBBUlRJVElPTiBCWSBkZXBhcnRtZW50IAogICAgICAgIE9SREVSIEJZIGhpcmVfZGF0ZQogICAgICAgIFJPV1MgQkVUV0VFTiBVTkJPVU5ERUQgUFJFQ0VESU5HIEFORCBDVVJSRU5UIFJPVwogICAgKSBhcyBjdW11bGF0aXZlX2RlcHRfc2FsYXJ5CkZST00gZW1wbG95ZWVzCk9SREVSIEJZIGRlcGFydG1lbnQsIGhpcmVfZGF0ZTsKYGBgCgpUaGlzIHF1ZXJ5IGNhbGN1bGF0ZXMgYSBydW5uaW5nIHRvdGFsIG9mIHNhbGFyaWVzIHdpdGhpbiBlYWNoIGRlcGFydG1lbnQgb3JkZXJlZCBieSBoaXJlIGRhdGUuIEZvciBlYWNoIGVtcGxveWVlLCB0aGUgcnVubmluZyB0b3RhbCBpbmNsdWRlcyBhbGwgZW1wbG95ZWVzIGhpcmVkIGluIHRoZSBzYW1lIGRlcGFydG1lbnQgdXAgdG8gYW5kIGluY2x1ZGluZyB0aGUgY3VycmVudCBlbXBsb3llZSdzIGhpcmUgZGF0ZS4gVGhlIGZyYW1lIGNsYXVzZSAiUk9XUyBCRVRXRUVOIFVOQk9VTkRFRCBQUkVDRURJTkcgQU5EIENVUlJFTlQgUk9XIiBzcGVjaWZpZXMgdGhpcyBiZWhhdmlvciBleHBsaWNpdGx5LgoKIyMgRGVjaXNpb24gRnJhbWV3b3JrCgpDaG9vc2UgR1JPVVAgQlkgd2hlbiBjcmVhdGluZyBzdW1tYXJ5IHJlcG9ydHMgdGhhdCByZXF1aXJlIG9uZSByb3cgcGVyIGdyb3VwLCB3aGVuIHJlZHVjaW5nIHJlc3VsdCBzZXRzIHRocm91Z2ggYWdncmVnYXRpb24sIG9yIHdoZW4gYnVpbGRpbmcgZGFzaGJvYXJkcyB3aXRoIGFnZ3JlZ2F0ZSBzdGF0aXN0aWNzLiBUaGUgb3BlcmF0aW9uIGlzIGFwcHJvcHJpYXRlIHdoZW4gZGV0YWlsLWxldmVsIGRhdGEgaXMgdW5uZWNlc3NhcnkgaW4gdGhlIG91dHB1dC4KCkNob29zZSBQQVJUSVRJT04gQlkgd2hlbiBwcmVzZXJ2aW5nIGRldGFpbCByb3dzIHdoaWxlIGFkZGluZyBhZ2dyZWdhdGUgY29udGV4dCwgd2hlbiByYW5raW5nIHJvd3Mgd2l0aGluIGdyb3Vwcywgd2hlbiBjb21wYXJpbmcgaW5kaXZpZHVhbCB2YWx1ZXMgdG8gZ3JvdXAgc3RhdGlzdGljcywgb3Igd2hlbiBjYWxjdWxhdGluZyBydW5uaW5nIHRvdGFscyBhbmQgbW92aW5nIGF2ZXJhZ2VzLiBXaW5kb3cgZnVuY3Rpb25zIGV4Y2VsIGF0IGFuYWx5dGljYWwgcXVlcmllcyByZXF1aXJpbmcgYm90aCBkZXRhaWwgYW5kIHN1bW1hcnkgaW5mb3JtYXRpb24gc2ltdWx0YW5lb3VzbHkuCgpDb21iaW5lIGJvdGggb3BlcmF0aW9ucyB3aGVuIGZpcnN0IGFnZ3JlZ2F0aW5nIGRhdGEgdGhlbiBwZXJmb3JtaW5nIGNvbXBhcmF0aXZlIGFuYWx5c2lzIG9uIHRoZSBhZ2dyZWdhdGVzLCB3aGVuIHJhbmtpbmcgYWdncmVnYXRlZCBncm91cHMsIG9yIHdoZW4gY2FsY3VsYXRpbmcgcGVyY2VudGFnZXMgb2YgZ3JvdXAgdG90YWxzIGZyb20gYWxyZWFkeS1hZ2dyZWdhdGVkIGRhdGEuCgojIyBDb21tb24gRXJyb3JzCgpBdHRlbXB0aW5nIHRvIHNlbGVjdCBub24tYWdncmVnYXRlZCBjb2x1bW5zIHdpdGhvdXQgaW5jbHVkaW5nIHRoZW0gaW4gR1JPVVAgQlkgY2F1c2VzIGVycm9ycy4gRXZlcnkgY29sdW1uIGluIHRoZSBTRUxFQ1QgY2xhdXNlIG11c3QgZWl0aGVyIGFwcGVhciBpbiBHUk9VUCBCWSBvciBiZSB3cmFwcGVkIGluIGFuIGFnZ3JlZ2F0ZSBmdW5jdGlvbi4KCmBgYCBzcWwKLS0gRXJyb3I6IG5hbWUgbXVzdCBiZSBncm91cGVkIG9yIGFnZ3JlZ2F0ZWQKU0VMRUNUIGRlcGFydG1lbnQsIG5hbWUsIEFWRyhzYWxhcnkpIAogIEZST00gZW1wbG95ZWVzIAogR1JPVVAgQlkgZGVwYXJ0bWVudDsKCi0tIENvcnJlY3Q6IGluY2x1ZGUgbmFtZSBpbiBHUk9VUCBCWQpTRUxFQ1QgZGVwYXJ0bWVudCwgbmFtZSwgQVZHKHNhbGFyeSkgCiAgRlJPTSBlbXBsb3llZXMgCiBHUk9VUCBCWSBkZXBhcnRtZW50LCBuYW1lOwpgYGAKCldoZW4gY29tYmluaW5nIEdST1VQIEJZIGFuZCBQQVJUSVRJT04gQlksIGVuc3VyZSBwYXJ0aXRpb24gY29sdW1ucyBleGlzdCBpbiB0aGUgZ3JvdXBlZCByZXN1bHQgc2V0LiBJZiBHUk9VUCBCWSBjb2xsYXBzZXMgYXdheSBhIGNvbHVtbiwgdGhhdCBjb2x1bW4gY2Fubm90IGJlIHVzZWQgaW4gUEFSVElUSU9OIEJZLgoKYGBgIHNxbAotLSBFcnJvcjogZW1wbG95ZWVfaWQgZ3JvdXBlZCBhd2F5IGJ1dCBuZWVkZWQgZm9yIHBhcnRpdGlvbgpTRUxFQ1QgZGVwYXJ0bWVudCwgU1VNKHNhbGFyeSksCiAgICAgICBSQU5LKCkgT1ZFUiAoUEFSVElUSU9OIEJZIGVtcGxveWVlX2lkIE9SREVSIEJZIHNhbGFyeSkKICBGUk9NIGVtcGxveWVlcyBHUk9VUCBCWSBkZXBhcnRtZW50OwoKLS0gQ29ycmVjdDogcGFydGl0aW9uIGJ5IHdoYXQgcmVtYWlucyBhZnRlciBncm91cGluZwpTRUxFQ1QgZGVwYXJ0bWVudCwgU1VNKHNhbGFyeSkgYXMgdG90YWxfc2FsYXJ5LAogICAgICAgUkFOSygpIE9WRVIgKE9SREVSIEJZIFNVTShzYWxhcnkpIERFU0MpIGFzIGRlcHRfcmFuawogIEZST00gZW1wbG95ZWVzIEdST1VQIEJZIGRlcGFydG1lbnQ7CmBgYAoKRXhwZWN0aW5nIFBBUlRJVElPTiBCWSB0byByZWR1Y2Ugcm93IGNvdW50cyBpcyBhIGNvbW1vbiBtaXNjb25jZXB0aW9uLiBXaW5kb3cgZnVuY3Rpb25zIGFsd2F5cyBwcmVzZXJ2ZSB0aGUgaW5wdXQgcm93IGNvdW50IHVubGVzcyBESVNUSU5DVCBvciBhZGRpdGlvbmFsIGZpbHRlcmluZyBpcyBhcHBsaWVkLgoKIyMgUGVyZm9ybWFuY2UgQ29uc2lkZXJhdGlvbnMKCldpbmRvdyBmdW5jdGlvbnMgdHlwaWNhbGx5IHBlcmZvcm0gYmV0dGVyIHRoYW4gZXF1aXZhbGVudCBzZWxmLWpvaW5zIG9yIGNvcnJlbGF0ZWQgc3VicXVlcmllcy4gTW9kZXJuIHF1ZXJ5IG9wdGltaXplcnMgcmVjb2duaXplIHdpbmRvdyBmdW5jdGlvbiBwYXR0ZXJucyBhbmQgZ2VuZXJhdGUgZWZmaWNpZW50IGV4ZWN1dGlvbiBwbGFucy4gSG93ZXZlciwgcGFydGl0aW9uaW5nIG9uIHVuaW5kZXhlZCBjb2x1bW5zIG9yIG9yZGVyaW5nIGJ5IGV4cHJlc3Npb25zIGNhbiBkZWdyYWRlIHBlcmZvcm1hbmNlIG9uIGxhcmdlIGRhdGFzZXRzLgoKQ29uc2lkZXIgY3JlYXRpbmcgaW5kZXhlcyBvbiBjb2x1bW5zIHVzZWQgaW4gUEFSVElUSU9OIEJZIGFuZCBPUkRFUiBCWSBjbGF1c2VzIGZvciBsYXJnZSB0YWJsZXMuIFdoZW4gY29tYmluaW5nIEdST1VQIEJZIGFuZCBQQVJUSVRJT04gQlksIHRoZSBpbml0aWFsIGFnZ3JlZ2F0aW9uIHJlZHVjZXMgdGhlIGRhdGFzZXQgc2l6ZSBiZWZvcmUgd2luZG93IGNhbGN1bGF0aW9ucywgb2Z0ZW4gaW1wcm92aW5nIG92ZXJhbGwgcXVlcnkgcGVyZm9ybWFuY2UgY29tcGFyZWQgdG8gd2luZG93IGZ1bmN0aW9ucyBvbiByYXcgZGF0YS4KCk11bHRpcGxlIHdpbmRvdyBmdW5jdGlvbnMgd2l0aCBpZGVudGljYWwgUEFSVElUSU9OIEJZIGFuZCBPUkRFUiBCWSBjbGF1c2VzIGNhbiBzaGFyZSBjb21wdXRhdGlvbi4gTW9kZXJuIG9wdGltaXplcnMgcmVjb2duaXplIHRoaXMgcGF0dGVybiBhbmQgYXZvaWQgcmVkdW5kYW50IHNvcnRpbmcgYW5kIHBhcnRpdGlvbmluZyBvcGVyYXRpb25zLgoKYGBgIHNxbAotLSBFZmZpY2llbnQ6IHNhbWUgd2luZG93IHNwZWNpZmljYXRpb24gcmV1c2VkClNFTEVDVCAKICAgIG5hbWUsCiAgICBzYWxhcnksCiAgICBSQU5LKCkgT1ZFUiAoUEFSVElUSU9OIEJZIGRlcGFydG1lbnQgT1JERVIgQlkgc2FsYXJ5IERFU0MpIGFzIHJhbmssCiAgICBERU5TRV9SQU5LKCkgT1ZFUiAoUEFSVElUSU9OIEJZIGRlcGFydG1lbnQgT1JERVIgQlkgc2FsYXJ5IERFU0MpIGFzIGRlbnNlX3JhbmssCiAgICBST1dfTlVNQkVSKCkgT1ZFUiAoUEFSVElUSU9OIEJZIGRlcGFydG1lbnQgT1JERVIgQlkgc2FsYXJ5IERFU0MpIGFzIHJvd19udW0KRlJPTSBlbXBsb3llZXM7CmBgYAoKVW5kZXJzdGFuZGluZyB0aGUgZGlzdGluY3Rpb24gYmV0d2VlbiBHUk9VUCBCWSBhZ2dyZWdhdGlvbiBhbmQgUEFSVElUSU9OIEJZIHdpbmRvdyBmdW5jdGlvbnMsIHRoZWlyIGV4ZWN1dGlvbiBvcmRlciwgYW5kIHRoZWlyIGFwcHJvcHJpYXRlIHVzZSBjYXNlcyBlbmFibGVzIGNvbnN0cnVjdGlvbiBvZiBzb3BoaXN0aWNhdGVkIGFuYWx5dGljYWwgcXVlcmllcyB0aGF0IGVmZmljaWVudGx5IGFuc3dlciBjb21wbGV4IGJ1c2luZXNzIHF1ZXN0aW9ucy4KCmBgYHtyIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmRiRGlzY29ubmVjdChkYmNvbikKYGBgCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIEZpbGVzICYgUmVzb3VyY2VzCgpgYGB7ciB6aXBGaWxlcywgZWNobz1GQUxTRX0KemlwTmFtZSA9IHNwcmludGYoIkxlc3NvbkZpbGVzLSVzLSVzLnppcCIsIAogICAgICAgICAgICAgICAgIHBhcmFtcyRjYXRlZ29yeSwKICAgICAgICAgICAgICAgICBwYXJhbXMkbnVtYmVyKQoKdGV4dEFMaW5rID0gcGFzdGUwKCJBbGwgRmlsZXMgZm9yIExlc3NvbiAiLCAKICAgICAgICAgICAgICAgcGFyYW1zJGNhdGVnb3J5LCIuIixwYXJhbXMkbnVtYmVyKQoKIyBkb3dubG9hZEZpbGVzTGluaygpIGlzIGluY2x1ZGVkIGZyb20gX2luc2VydDJEQi5SCmtuaXRyOjpyYXdfaHRtbChkb3dubG9hZEZpbGVzTGluaygiLiIsIHppcE5hbWUsIHRleHRBTGluaykpCmBgYAoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyBOb3RlcwoKU1FMaXRlIGFkZGVkIGNvbXByZWhlbnNpdmUgd2luZG93IGZ1bmN0aW9uIHN1cHBvcnQgaW5jbHVkaW5nIFBBUlRJVElPTiBCWSBpbiB2ZXJzaW9uIDMuMjUuMCByZWxlYXNlZCBpbiBTZXB0ZW1iZXIgMjAxOC4gQW55IG1vZGVybiBTUUxpdGUgaW5zdGFsbGF0aW9uIHN1cHBvcnRzIHRoZXNlIGZlYXR1cmVzLiBDaGVjayB0aGUgdmVyc2lvbiB3aXRoIGBTRUxFQ1Qgc3FsaXRlX3ZlcnNpb24oKTtgIHRvIGNvbmZpcm0gY29tcGF0aWJpbGl0eS4gVmVyc2lvbnMgcHJpb3IgdG8gMy4yNS4wIHJlcXVpcmUgY29tcGxleCB3b3JrYXJvdW5kcyB1c2luZyBzZWxmLWpvaW5zIGFuZCBzdWJxdWVyaWVzIHRoYXQgYXJlIGJvdGggaW5lZmZpY2llbnQgYW5kIGRpZmZpY3VsdCB0byBtYWludGFpbi4KCiMjIEFja25vd2xlZGdlbWVudHMKCkNsYXVkZSA0LjYgKFNvbm5ldCkgd2FzIHVzZWQgaW4gdGhlIHByZXBhcmF0aW9uIG9mIHRoaXMgbGVzc29uLgoKIyMgRXJyYXRhCgpbTGV0IHVzIGtub3ddKGh0dHBzOi8vZm9ybS5qb3Rmb3JtLmNvbS8yMTIxODcwNzI3ODQxNTcpe3RhcmdldD0iX2JsYW5rIn0uCg==