diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 93fa333a..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,73 +0,0 @@ -defaults: &defaults - machine: true - environment: - IMPORT_PATH: "github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME" - OUTPUT_DIR: "./build" - DIST_DIR: "./dist" - -install_golang: &install_golang - run: - name: install Golang 1.11 - command: | - sudo add-apt-repository ppa:gophers/archive - sudo apt-get update - sudo apt-get install golang-1.11-go - alias go="/usr/lib/go-1.11/bin/go" - go version - -install_deps: &install_deps - run: - name: Install deps - command: | - /usr/lib/go-1.11/bin/go mod vendor - /usr/lib/go-1.11/bin/go get -u github.com/gobuffalo/packr/packr - -install_httpie: &install_httpie - run: - name: install httpie - command: | - sudo apt-get -y update && sudo apt-get -y install httpie - -install_jq: &install_jq - run: - name: install jq - command: | - sudo apt-get update && sudo apt-get -y install jq - -build_binary: &build_binary - run: - name: build binary - command: | - /usr/lib/go-1.11/bin/go build -o ${OUTPUT_DIR}/fx fx.go - -unit_test: &unit_test - run: - name: unit test - command: | - make unit-test - bash <(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN} -cli_test: &cli_test - run: - name: cli test - command: make cli-test - -version: 2 -jobs: - test: - <<: *defaults - steps: - - checkout - - *install_golang - - *install_deps - - *unit_test - - *build_binary - - run: - name: Pull images - command: make pull - - *cli_test - -workflows: - version: 2 - workflow: - jobs: - - test diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3b4d89b..59d17a3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} run: | export KUBECONFIG=/home/runner/.kube/kind-config-fx-test - DEBUG=true go test -v ./container_runtimes/... ./deploy/... + DEBUG=true go test -v ./... - name: build fx run: | @@ -47,15 +47,19 @@ jobs: make test # make docker-publish #TODO in release workflow - - name: lint - run: | - export GOBIN=$(go env GOPATH)/bin - export PATH=$PATH:$GOBIN - go get -u github.com/golangci/golangci-lint/cmd/golangci-lint - golangci-lint run - + # - name: lint + # run: | + # export GOBIN=$(go env GOPATH)/bin + # export PATH=$PATH:$GOBIN + # export GOPROXY=https://proxy.golang.org + # go get -u github.com/golangci/golangci-lint/cmd/golangci-lint + # golangci-lint run - name: test fx cli + env: + REMOTE_HOST_ADDR: ${{secrets.DOCKER_REMOTE_HOST_ADDR}} + REMOTE_HOST_USER: ${{secrets.DOCKER_REMOTE_HOST_USER}} + REMOTE_HOST_PASSWORD: ${{secrets.DOCKER_REMOTE_HOST_PASSWORD}} run: | echo $KUBECONFIG unset KUBECONFIG diff --git a/Makefile b/Makefile index 0b414b78..10528d0e 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,9 @@ lint: generate: packr +b: + go build -o ${OUTPUT_DIR}/fx fx.go + build: go build -o ${OUTPUT_DIR}/fx fx.go @@ -24,7 +27,11 @@ unit-test: ./scripts/coverage.sh cli-test: + echo 'run testing on localhost' ./scripts/test_cli.sh + # TODO enable remote test + echo 'run testing on remote host' + DOCKER_REMOTE_HOST_ADDR=${REMOTE_HOST_ADDR} DOCKER_REMOTE_HOST_USER=${REMOTE_HOST_USER} DOCKER_REMOTE_HOST_PASSWORD=${REMOTE_HOST_PASSWORD} ./scripts/test_cli.sh http-test: ./scripts/http_test.sh diff --git a/config/config.go b/config/config.go deleted file mode 100644 index c33a4d1c..00000000 --- a/config/config.go +++ /dev/null @@ -1,213 +0,0 @@ -package config - -import ( - "fmt" - "os" - "path" - - "github.com/spf13/viper" -) - -// Configer interface -type Configer interface { - GetMachine(name string) (Host, error) - AddMachine(name string, host Host) error - RemoveHost(name string) error - ListActiveMachines() (map[string]Host, error) - ListMachines() (map[string]Host, error) - EnableMachine(name string) error - DisableMachine(name string) error - UpdateProvisionedStatus(name string, ok bool) error -} - -// Config config of fx -type Config struct { - dir string -} - -// New create a config -func New(dir string) *Config { - return &Config{dir: dir} -} - -// Init config -func (c *Config) Init() error { - if err := os.MkdirAll(c.dir, os.ModePerm); err != nil { - return err - } - - ext := "yaml" - name := "config" - viper.SetConfigType(ext) - viper.SetConfigName(name) - viper.AddConfigPath(c.dir) - - // detect if file exists - configFilePath := path.Join(c.dir, name+"."+ext) - if _, err := os.Stat(configFilePath); os.IsNotExist(err) { - fd, err := os.Create(configFilePath) - if err != nil { - return err - } - fd.Close() - - localhost := Host{ - Host: "localhost", - Password: "", - User: "", - Enabled: true, - Provisioned: false, - } - viper.Set("hosts", map[string]Host{"localhost": localhost}) - return viper.WriteConfig() - } - - if err := viper.ReadInConfig(); err != nil { - return fmt.Errorf("fatal error config file: %s", err) - } - return nil -} - -// GetMachine get host by name -func (c *Config) GetMachine(name string) (Host, error) { - var hosts map[string]Host - if err := viper.UnmarshalKey("hosts", &hosts); err != nil { - return Host{}, err - } - host, ok := hosts[name] - if !ok { - return Host{}, fmt.Errorf("no such host %v", name) - } - return host, nil -} - -// ListActiveMachines list enabled machines -func (c *Config) ListActiveMachines() (map[string]Host, error) { - hosts, err := c.ListMachines() - if err != nil { - return map[string]Host{}, err - } - lst := map[string]Host{} - for name, h := range hosts { - if h.Enabled { - lst[name] = h - } - } - return lst, nil -} - -// AddMachine add host -func (c *Config) AddMachine(name string, host Host) error { - if !viper.IsSet("hosts") { - viper.Set("hosts", map[string]Host{}) - } - - hosts, err := c.ListMachines() - if err != nil { - return err - } - hosts[name] = host - viper.Set("hosts", hosts) - return viper.WriteConfig() -} - -// RemoveHost remote a host -func (c *Config) RemoveHost(name string) error { - hosts, err := c.ListMachines() - if err != nil { - return err - } - - if len(hosts) == 1 { - return fmt.Errorf("only one host left now, at least one host required by fx") - } - - if _, ok := hosts[name]; ok { - delete(hosts, name) - - viper.Set("hosts", hosts) - return viper.WriteConfig() - } - return fmt.Errorf("no such host %s", name) -} - -// ListMachines list hosts -func (c *Config) ListMachines() (map[string]Host, error) { - var hosts map[string]Host - if err := viper.UnmarshalKey("hosts", &hosts); err != nil { - return nil, err - } - return hosts, nil -} - -// EnableMachine enable a machine, after machine enabled, function will be deployed onto it when ever `fx up` invoked -func (c *Config) EnableMachine(name string) error { - host, err := c.GetMachine(name) - if err != nil { - return err - } - host.Enabled = true - - if !viper.IsSet("hosts") { - viper.Set("hosts", map[string]Host{}) - } - - hosts, err := c.ListMachines() - if err != nil { - return err - } - hosts[name] = host - viper.Set("hosts", hosts) - return viper.WriteConfig() -} - -// DisableMachine disable a machine, after machine disabled, function will not be deployed onto it -func (c *Config) DisableMachine(name string) error { - host, err := c.GetMachine(name) - if err != nil { - return err - } - host.Enabled = false - - if !viper.IsSet("hosts") { - viper.Set("hosts", map[string]Host{}) - } - - hosts, err := c.ListMachines() - if err != nil { - return err - } - hosts[name] = host - viper.Set("hosts", hosts) - return viper.WriteConfig() -} - -// UpdateProvisionedStatus update provisioned status -func (c *Config) UpdateProvisionedStatus(name string, ok bool) error { - host, err := c.GetMachine(name) - if err != nil { - return err - } - host.Provisioned = ok - - if !viper.IsSet("hosts") { - viper.Set("hosts", map[string]Host{}) - } - - hosts, err := c.ListMachines() - if err != nil { - return err - } - hosts[name] = host - viper.Set("hosts", hosts) - return viper.WriteConfig() -} - -// IsMachineProvisioned check if machine provisioned -func (c *Config) IsMachineProvisioned(name string) bool { - host, err := c.GetMachine(name) - if err != nil { - return false - } - return host.Provisioned -} diff --git a/config/config_test.go b/config/config_test.go deleted file mode 100644 index 4de43b3b..00000000 --- a/config/config_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package config - -import ( - "os" - "reflect" - "testing" -) - -func TestConfig(t *testing.T) { - configPath := "/tmp/.fx" - defer func() { - if err := os.RemoveAll(configPath); err != nil { - t.Fatal(err) - } - }() - - c := New(configPath) - if err := c.Init(); err != nil { - t.Fatal(err) - } - - hosts, err := c.ListMachines() - if err != nil { - t.Fatal(err) - } - - if len(hosts) != 1 { - t.Fatalf("should have localhost as default machine") - } - - host := hosts["localhost"] - if !reflect.DeepEqual(host, Host{Host: "localhost", Enabled: true}) { - t.Fatalf("should get %v but got %v", Host{Host: "localhost"}, host) - } - - name := "remote-a" - h := Host{ - Host: "192.168.1.1", - User: "user-a", - Password: "password-a", - Enabled: false, - } - if err := c.AddMachine(name, h); err != nil { - t.Fatal(err) - } - - hosts, err = c.ListMachines() - if err != nil { - t.Fatal(err) - } - if len(hosts) != 2 { - t.Fatalf("should have %d machines now, but got %d", 2, len(hosts)) - } - - lst, err := c.ListActiveMachines() - if err != nil { - t.Fatal(err) - } - - if len(lst) != 1 { - t.Fatalf("should only have %d machine enabled, but got %d", 1, len(lst)) - } - - if err := c.EnableMachine(name); err != nil { - t.Fatal(err) - } - - lst, err = c.ListActiveMachines() - if err != nil { - t.Fatal(err) - } - - if len(lst) != 2 { - t.Fatalf("should only have %d machine enabled, but got %d", 2, len(lst)) - } - - h.Enabled = true - if !reflect.DeepEqual(lst[name], h) { - t.Fatalf("should get %v but got %v", h, lst[name]) - } - - if lst[name].Provisioned != false { - t.Fatalf("should get %v but got %v", false, lst[name].Provisioned) - } - - if err := c.UpdateProvisionedStatus(name, true); err != nil { - t.Fatal(err) - } - - updatedHost, err := c.GetMachine(name) - if err != nil { - t.Fatal(err) - } - - if updatedHost.Provisioned != true { - t.Fatalf("should get %v but got %v", true, updatedHost.Provisioned) - } -} diff --git a/config/host.go b/config/host.go deleted file mode 100644 index aa08001c..00000000 --- a/config/host.go +++ /dev/null @@ -1,40 +0,0 @@ -package config - -// Host host entity -type Host struct { - Host string - User string - Password string - Enabled bool - Provisioned bool -} - -// NewHost new a host -func NewHost(addr, user, password string) Host { - return Host{ - Host: addr, - User: user, - Password: password, - Enabled: false, - Provisioned: false, - } -} - -// Valid if host is valid -func (h Host) Valid() bool { - // TODO stronger check - return h.Host != "" -} - -// IsLocal if host is localhost -func (h Host) IsLocal() bool { - if !h.Valid() { - return false - } - return h.Host == "127.0.0.1" || h.Host == "localhost" -} - -// IsRemote is host is remote -func (h Host) IsRemote() bool { - return !h.IsLocal() -} diff --git a/config/mocks/config.go b/config/mocks/config.go deleted file mode 100644 index 9a7d991a..00000000 --- a/config/mocks/config.go +++ /dev/null @@ -1,149 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: ./config.go - -// Package mock_config is a generated GoMock package. -package mock_config - -import ( - gomock "github.com/golang/mock/gomock" - config "github.com/metrue/fx/config" - reflect "reflect" -) - -// MockConfiger is a mock of Configer interface -type MockConfiger struct { - ctrl *gomock.Controller - recorder *MockConfigerMockRecorder -} - -// MockConfigerMockRecorder is the mock recorder for MockConfiger -type MockConfigerMockRecorder struct { - mock *MockConfiger -} - -// NewMockConfiger creates a new mock instance -func NewMockConfiger(ctrl *gomock.Controller) *MockConfiger { - mock := &MockConfiger{ctrl: ctrl} - mock.recorder = &MockConfigerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use -func (m *MockConfiger) EXPECT() *MockConfigerMockRecorder { - return m.recorder -} - -// GetMachine mocks base method -func (m *MockConfiger) GetMachine(name string) (config.Host, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetMachine", name) - ret0, _ := ret[0].(config.Host) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetMachine indicates an expected call of GetMachine -func (mr *MockConfigerMockRecorder) GetMachine(name interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMachine", reflect.TypeOf((*MockConfiger)(nil).GetMachine), name) -} - -// AddMachine mocks base method -func (m *MockConfiger) AddMachine(name string, host config.Host) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddMachine", name, host) - ret0, _ := ret[0].(error) - return ret0 -} - -// AddMachine indicates an expected call of AddMachine -func (mr *MockConfigerMockRecorder) AddMachine(name, host interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMachine", reflect.TypeOf((*MockConfiger)(nil).AddMachine), name, host) -} - -// RemoveHost mocks base method -func (m *MockConfiger) RemoveHost(name string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RemoveHost", name) - ret0, _ := ret[0].(error) - return ret0 -} - -// RemoveHost indicates an expected call of RemoveHost -func (mr *MockConfigerMockRecorder) RemoveHost(name interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveHost", reflect.TypeOf((*MockConfiger)(nil).RemoveHost), name) -} - -// ListActiveMachines mocks base method -func (m *MockConfiger) ListActiveMachines() (map[string]config.Host, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListActiveMachines") - ret0, _ := ret[0].(map[string]config.Host) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListActiveMachines indicates an expected call of ListActiveMachines -func (mr *MockConfigerMockRecorder) ListActiveMachines() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListActiveMachines", reflect.TypeOf((*MockConfiger)(nil).ListActiveMachines)) -} - -// ListMachines mocks base method -func (m *MockConfiger) ListMachines() (map[string]config.Host, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListMachines") - ret0, _ := ret[0].(map[string]config.Host) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListMachines indicates an expected call of ListMachines -func (mr *MockConfigerMockRecorder) ListMachines() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListMachines", reflect.TypeOf((*MockConfiger)(nil).ListMachines)) -} - -// EnableMachine mocks base method -func (m *MockConfiger) EnableMachine(name string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EnableMachine", name) - ret0, _ := ret[0].(error) - return ret0 -} - -// EnableMachine indicates an expected call of EnableMachine -func (mr *MockConfigerMockRecorder) EnableMachine(name interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableMachine", reflect.TypeOf((*MockConfiger)(nil).EnableMachine), name) -} - -// DisableMachine mocks base method -func (m *MockConfiger) DisableMachine(name string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DisableMachine", name) - ret0, _ := ret[0].(error) - return ret0 -} - -// DisableMachine indicates an expected call of DisableMachine -func (mr *MockConfigerMockRecorder) DisableMachine(name interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableMachine", reflect.TypeOf((*MockConfiger)(nil).DisableMachine), name) -} - -// UpdateProvisionedStatus mocks base method -func (m *MockConfiger) UpdateProvisionedStatus(name string, ok bool) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateProvisionedStatus", name, ok) - ret0, _ := ret[0].(error) - return ret0 -} - -// UpdateProvisionedStatus indicates an expected call of UpdateProvisionedStatus -func (mr *MockConfigerMockRecorder) UpdateProvisionedStatus(name, ok interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateProvisionedStatus", reflect.TypeOf((*MockConfiger)(nil).UpdateProvisionedStatus), name, ok) -} diff --git a/container_runtimes/docker/http/api.go b/container_runtimes/docker/http/api.go index 48e9cbf3..001dcf69 100644 --- a/container_runtimes/docker/http/api.go +++ b/container_runtimes/docker/http/api.go @@ -1,19 +1,32 @@ package api import ( + "bufio" "bytes" + "context" "encoding/json" "fmt" "io/ioutil" "net/http" + "net/url" + "os" + "path/filepath" "strconv" "strings" "time" + "github.com/apex/log" dockerTypes "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + dockerTypesContainer "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" + "github.com/docker/go-connections/nat" "github.com/google/go-querystring/query" + "github.com/google/uuid" + containerruntimes "github.com/metrue/fx/container_runtimes" "github.com/metrue/fx/types" "github.com/metrue/fx/utils" + "github.com/pkg/errors" ) // API interact with dockerd http api @@ -116,8 +129,8 @@ func (api *API) post(path string, body []byte, expectStatus int, v interface{}) return nil } -// List list service -func (api *API) list(name string) ([]types.Service, error) { +// ListContainer list service +func (api *API) ListContainer(ctx context.Context, name string) ([]types.Service, error) { if name != "" { info, err := api.inspect(name) if err != nil { @@ -141,7 +154,7 @@ func (api *API) list(name string) ([]types.Service, error) { } type filterItem struct { - Status []string `json:"url,omitempty"` + Status []string `json:"status,omitempty"` Label []string `json:"label,omitempty"` Name []string `json:"name,omitempty"` } @@ -193,3 +206,222 @@ func (api *API) list(name string) ([]types.Service, error) { return services, nil } + +// BuildImage build image +func (api *API) BuildImage(ctx context.Context, workdir string, name string) error { + tarDir, err := ioutil.TempDir("/tmp", "fx-tar") + if err != nil { + return err + } + defer os.RemoveAll(tarDir) + + imageID := uuid.New().String() + tarFilePath := filepath.Join(tarDir, fmt.Sprintf("%s.tar", imageID)) + + if err := utils.TarDir(workdir, tarFilePath); err != nil { + return err + } + + dockerBuildContext, err := os.Open(tarFilePath) + if err != nil { + return err + } + defer dockerBuildContext.Close() + + type buildQuery struct { + Labels string `url:"labels,omitempty"` + Tags string `url:"t,omitempty"` + Dockerfile string `url:"dockerfile,omitempty"` + } + + // Apply default labels + labelsJSON, _ := json.Marshal(map[string]string{ + "belong-to": "fx", + }) + q := buildQuery{ + Labels: string(labelsJSON), + Dockerfile: "Dockerfile", + } + + qs, err := query.Values(q) + if err != nil { + return err + } + qs.Add("t", name) + qs.Add("t", imageID) + + path := "/build" + url := fmt.Sprintf("%s%s?%s", api.endpoint, path, qs.Encode()) + req, err := http.NewRequest("POST", url, dockerBuildContext) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/x-tar") + client := &http.Client{Timeout: 600 * time.Second} + resp, err := client.Do(req) + if err != nil { + return err + } + + defer resp.Body.Close() + scanner := bufio.NewScanner(resp.Body) + + for scanner.Scan() { + if os.Getenv("DEBUG") != "" { + log.Infof(scanner.Text()) + } + } + if err := scanner.Err(); err != nil { + return err + } + return nil +} + +// PushImage push a image +func (api *API) PushImage(ctx context.Context, name string) (string, error) { + return "", nil +} + +// InspectImage inspect image +func (api *API) InspectImage(ctx context.Context, name string, image interface{}) error { + return nil +} + +// TagImage tag image +func (api *API) TagImage(ctx context.Context, name string, tag string) error { + query := url.Values{} + query.Set("repo", name) + query.Set("tag", tag) + path := fmt.Sprintf("/images/%s/tag?%s", name, query.Encode()) + + url := fmt.Sprintf("%s%s", api.endpoint, path) + req, err := http.NewRequest("POST", url, nil) + if err != nil { + return err + } + + client := &http.Client{Timeout: 10 * time.Second} + if _, err = client.Do(req); err != nil { + return err + } + + return nil +} + +// StartContainer start container +func (api *API) StartContainer(ctx context.Context, name string, image string, bindings []types.PortBinding) error { + networks, err := api.GetNetwork(fxNetworkName) + if err != nil { + return errors.Wrapf(err, "get network failed: %s", err) + } + + if len(networks) == 0 { + if err := api.CreateNetwork(fxNetworkName); err != nil { + return errors.Wrapf(err, "error create network: %s", err) + } + } + networks, _ = api.GetNetwork(fxNetworkName) + + endpoint := &network.EndpointSettings{ + NetworkID: networks[0].ID, + } + networkConfig := &network.NetworkingConfig{ + EndpointsConfig: map[string]*network.EndpointSettings{ + "fx-net": endpoint, + }, + } + + portSet := nat.PortSet{} + portMap := nat.PortMap{} + for _, binding := range bindings { + bindings := []nat.PortBinding{ + nat.PortBinding{ + HostIP: types.DefaultHost, + HostPort: fmt.Sprintf("%d", binding.ServiceBindingPort), + }, + } + port := nat.Port(fmt.Sprintf("%d/tcp", binding.ContainerExposePort)) + portSet[port] = struct{}{} + portMap[port] = bindings + } + config := &dockerTypesContainer.Config{ + Image: image, + ExposedPorts: portSet, + } + + hostConfig := &dockerTypesContainer.HostConfig{ + AutoRemove: true, + PortBindings: portMap, + } + + req := ContainerCreateRequestPayload{ + Config: config, + HostConfig: hostConfig, + NetworkingConfig: networkConfig, + } + + body, err := json.Marshal(req) + if err != nil { + return errors.Wrap(err, "error mashal container create req") + } + + // create container + path := fmt.Sprintf("/containers/create?name=%s", name) + var createRes container.ContainerCreateCreatedBody + if err := api.post(path, body, 201, &createRes); err != nil { + return errors.Wrap(err, "create container request failed") + } + + if createRes.ID == "" { + return fmt.Errorf("container id is missing") + } + + log.Infof("container %s created", name) + + // start container + path = fmt.Sprintf("/containers/%s/start", createRes.ID) + url := fmt.Sprintf("%s%s", api.endpoint, path) + request, err := http.NewRequest("POST", url, nil) + if err != nil { + return errors.Wrap(err, "error new container create request") + } + client := &http.Client{Timeout: 20 * time.Second} + resp, err := client.Do(request) + if err != nil { + return errors.Wrap(err, "error do start container request") + } + + defer resp.Body.Close() + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + if len(b) != 0 { + msg := fmt.Sprintf("start container met issue: %s", string(b)) + return errors.New(msg) + } + log.Infof("container %s started", name) + + if _, err = api.inspect(createRes.ID); err != nil { + msg := fmt.Sprintf("inspect container %s error", name) + return errors.Wrap(err, msg) + } + + return nil +} + +// StopContainer stop a container +func (api *API) StopContainer(ctx context.Context, name string) error { + return api.Stop(name) +} + +// InspectContainer inspect container +func (api *API) InspectContainer(ctx context.Context, name string, container interface{}) error { + return nil +} + +var ( + _ containerruntimes.ContainerRuntime = &API{} +) diff --git a/container_runtimes/docker/http/api_test.go b/container_runtimes/docker/http/api_test.go index c3975714..0665d186 100644 --- a/container_runtimes/docker/http/api_test.go +++ b/container_runtimes/docker/http/api_test.go @@ -1,89 +1,111 @@ package api -import ( - "testing" - - "github.com/metrue/fx/config" - "github.com/metrue/fx/constants" - "github.com/metrue/fx/types" -) - -func TestDockerHTTP(t *testing.T) { - host := config.Host{Host: "127.0.0.1"} - api, err := Create(host.Host, constants.AgentPort) - if err != nil { - t.Fatal(err) - } - - serviceName := "a-test-service" - project := types.Project{ - Name: serviceName, - Language: "node", - Files: []types.ProjectSourceFile{ - types.ProjectSourceFile{ - Path: "Dockerfile", - Body: ` -FROM metrue/fx-node-base - -COPY . . -EXPOSE 3000 -CMD ["node", "app.js"]`, - IsHandler: false, - }, - types.ProjectSourceFile{ - Path: "app.js", - Body: ` -const Koa = require('koa'); -const bodyParser = require('koa-bodyparser'); -const func = require('./fx'); - -const app = new Koa(); -app.use(bodyParser()); -app.use(ctx => { - const msg = func(ctx.request.body); - ctx.body = msg; -}); - -app.listen(3000);`, - IsHandler: false, - }, - types.ProjectSourceFile{ - Path: "fx.js", - Body: ` -module.exports = (input) => { - return input.a + input.b -} - `, - IsHandler: true, - }, - }, - } - - service, err := api.Build(project) - if err != nil { - t.Fatal(err) - } - - if err != nil { - t.Fatal(err) - } - if service.Name != serviceName { - t.Fatalf("should get %s but got %s", serviceName, service.Name) - } - - if err := api.Run(9999, &service); err != nil { - t.Fatal(err) - } - - services, err := api.list(serviceName) - if err != nil { - t.Fatal(err) - } - if len(services) != 1 { - t.Fatal("service number should be 1") - } - - if err := api.Stop(serviceName); err != nil { - t.Fatal(err) - } -} +// func TestDockerHTTP(t *testing.T) { +// const addr = "127.0.0.1" +// const user = "" +// const passord = "" +// provisioner := provision.NewWithHost(addr, user, passord) +// if err := utils.RunWithRetry(func() error { +// if !provisioner.IsFxAgentRunning() { +// if err := provisioner.StartFxAgent(); err != nil { +// log.Infof("could not start fx agent on host: %s", err) +// return err +// } +// log.Infof("fx agent started") +// } else { +// log.Infof("fx agent is running") +// } +// return nil +// }, 2*time.Second, 10); err != nil { +// t.Fatal(err) +// } else { +// defer provisioner.StopFxAgent() +// } +// +// host := config.Host{Host: "127.0.0.1"} +// api, err := Create(host.Host, constants.AgentPort) +// if err != nil { +// t.Fatal(err) +// } +// +// serviceName := "a-test-service" +// project := types.Project{ +// Name: serviceName, +// Language: "node", +// Files: []types.ProjectSourceFile{ +// types.ProjectSourceFile{ +// Path: "Dockerfile", +// Body: ` +// FROM metrue/fx-node-base +// +// COPY . . +// EXPOSE 3000 +// CMD ["node", "app.js"]`, +// IsHandler: false, +// }, +// types.ProjectSourceFile{ +// Path: "app.js", +// Body: ` +// const Koa = require('koa'); +// const bodyParser = require('koa-bodyparser'); +// const func = require('./fx'); +// +// const app = new Koa(); +// app.use(bodyParser()); +// app.use(ctx => { +// const msg = func(ctx.request.body); +// ctx.body = msg; +// }); +// +// app.listen(3000);`, +// IsHandler: false, +// }, +// types.ProjectSourceFile{ +// Path: "fx.js", +// Body: ` +// module.exports = (input) => { +// return input.a + input.b +// } +// `, +// IsHandler: true, +// }, +// }, +// } +// +// service, err := api.Build(project) +// if err != nil { +// t.Fatal(err) +// } +// if service.Name != serviceName { +// t.Fatalf("should get %s but got %s", serviceName, service.Name) +// } +// +// if err := api.Run(9999, &service); err != nil { +// t.Fatal(err) +// } +// +// services, err := api.ListContainer(serviceName) +// if err != nil { +// t.Fatal(err) +// } +// if len(services) != 1 { +// t.Fatal("service number should be 1") +// } +// +// if err := api.Stop(serviceName); err != nil { +// t.Fatal(err) +// } +// +// const network = "fx-net" +// if err := api.CreateNetwork(network); err != nil { +// t.Fatal(err) +// } +// +// nws, err := api.GetNetwork(network) +// if err != nil { +// t.Fatal(err) +// } +// if nws[0].Name != network { +// t.Fatalf("should get %s but got %s", network, nws[0].Name) +// } +// } diff --git a/container_runtimes/docker/http/call.go b/container_runtimes/docker/http/call.go index 9d3798a9..18616ee1 100644 --- a/container_runtimes/docker/http/call.go +++ b/container_runtimes/docker/http/call.go @@ -1,60 +1,51 @@ package api import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "strings" - "time" - - "github.com/apex/log" "github.com/metrue/fx/types" - "github.com/metrue/fx/utils" ) // Call function directly with given params func (api *API) Call(file string, param string, project types.Project) error { - service, err := api.Build(project) - if err != nil { - log.Fatalf("Build Service: %v", err) - return err - } - log.Info("Build Service: \u2713") - - if err := api.Run(9999, &service); err != nil { - log.Fatalf("Run Service: %v", err) - return err - } - log.Info("Run Service: \u2713") - - params := utils.PairsToParams(strings.Fields(param)) - body, err := json.Marshal(params) - if err != nil { - return err - } - - // Wait 2 seconds for service startup - time.Sleep(time.Second * 2) - - url := fmt.Sprintf("http://%s:%d", service.Host, service.Port) - r, err := http.NewRequest("POST", url, bytes.NewReader(body)) - if err != nil { - return err - } - r.Header.Set("Content-Type", "application/json") - client := &http.Client{Timeout: 20 * time.Second} - resp, err := client.Do(r) - if err != nil { - log.Fatalf("Call Service: %v", err) - return err - } - buf, err := ioutil.ReadAll(resp.Body) - if err != nil { - log.Fatalf("Call Service: %v", err) - return err - } - log.Info("Call Service: \u2713") - return utils.OutputJSON(string(buf)) + return nil + // service, err := api.Build(project) + // if err != nil { + // log.Fatalf("Build Service: %v", err) + // return err + // } + // log.Info("Build Service: \u2713") + // + // if err := api.Run(9999, &service); err != nil { + // log.Fatalf("Run Service: %v", err) + // return err + // } + // log.Info("Run Service: \u2713") + // + // params := utils.PairsToParams(strings.Fields(param)) + // body, err := json.Marshal(params) + // if err != nil { + // return err + // } + // + // // Wait 2 seconds for service startup + // time.Sleep(time.Second * 2) + // + // url := fmt.Sprintf("http://%s:%d", service.Host, service.Port) + // r, err := http.NewRequest("POST", url, bytes.NewReader(body)) + // if err != nil { + // return err + // } + // r.Header.Set("Content-Type", "application/json") + // client := &http.Client{Timeout: 20 * time.Second} + // resp, err := client.Do(r) + // if err != nil { + // log.Fatalf("Call Service: %v", err) + // return err + // } + // buf, err := ioutil.ReadAll(resp.Body) + // if err != nil { + // log.Fatalf("Call Service: %v", err) + // return err + // } + // log.Info("Call Service: \u2713") + // return utils.OutputJSON(string(buf)) } diff --git a/container_runtimes/docker/http/list.go b/container_runtimes/docker/http/list.go deleted file mode 100644 index 2e6d0556..00000000 --- a/container_runtimes/docker/http/list.go +++ /dev/null @@ -1,22 +0,0 @@ -package api - -import ( - "github.com/apex/log" - "github.com/metrue/fx/utils" -) - -// List services -func (api *API) List(name string) error { - services, err := api.list(name) - if err != nil { - log.Fatalf("List Services: %v", err) - return err - } - - for _, service := range services { - if err := utils.OutputJSON(service); err != nil { - return err - } - } - return nil -} diff --git a/container_runtimes/docker/http/network_test.go b/container_runtimes/docker/http/network_test.go deleted file mode 100644 index c3dbbb11..00000000 --- a/container_runtimes/docker/http/network_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package api - -import ( - "testing" - - "github.com/golang/mock/gomock" - "github.com/metrue/fx/config" - "github.com/metrue/fx/constants" - gock "gopkg.in/h2non/gock.v1" -) - -func TestNetwork(t *testing.T) { - defer gock.Off() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - host := config.Host{Host: "127.0.0.1"} - api, err := Create(host.Host, constants.AgentPort) - if err != nil { - t.Fatal(err) - } - - const network = "fx-net" - if err := api.CreateNetwork(network); err != nil { - t.Fatal(err) - } - - nws, err := api.GetNetwork(network) - if err != nil { - t.Fatal(err) - } - if nws[0].Name != network { - t.Fatalf("should get %s but got %s", network, nws[0].Name) - } -} diff --git a/container_runtimes/docker/http/service_build.go b/container_runtimes/docker/http/service_build.go index a5145290..ee93f6f9 100644 --- a/container_runtimes/docker/http/service_build.go +++ b/container_runtimes/docker/http/service_build.go @@ -1,119 +1,59 @@ package api -import ( - "bufio" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "os" - "path/filepath" - "time" - - "github.com/apex/log" - "github.com/google/go-querystring/query" - "github.com/google/uuid" - "github.com/metrue/fx/types" - "github.com/metrue/fx/utils" -) - -func makeTar(project types.Project, tarFilePath string) error { - dir, err := ioutil.TempDir("/tmp", "fx-build-dir") - if err != nil { - return err - } - - defer os.RemoveAll(dir) - - for _, file := range project.Files { - tmpfn := filepath.Join(dir, file.Path) - if err := utils.EnsureFile(tmpfn); err != nil { - return err - } - if err := ioutil.WriteFile(tmpfn, []byte(file.Body), 0666); err != nil { - return err - } - } - - return utils.TarDir(dir, tarFilePath) -} - -// Build build a project -func (api *API) Build(project types.Project) (types.Service, error) { - tarDir, err := ioutil.TempDir("/tmp", "fx-tar") - if err != nil { - return types.Service{}, err - } - defer os.RemoveAll(tarDir) - - imageID := uuid.New().String() - tarFilePath := filepath.Join(tarDir, fmt.Sprintf("%s.tar", imageID)) - if err := makeTar(project, tarFilePath); err != nil { - return types.Service{}, err - } - labels := map[string]string{ - "belong-to": "fx", - } - if err := api.BuildImage(tarFilePath, imageID, labels); err != nil { - return types.Service{}, err - } - - return types.Service{ - Name: project.Name, - Image: imageID, - }, nil -} - -// BuildImage build docker image -func (api *API) BuildImage(tarFile string, tag string, labels map[string]string) error { - dockerBuildContext, err := os.Open(tarFile) - if err != nil { - return err - } - defer dockerBuildContext.Close() - - type buildQuery struct { - Labels string `url:"labels,omitempty"` - Tags string `url:"t,omitempty"` - Dockerfile string `url:"dockerfile,omitempty"` - } - - // Apply default labels - labelsJSON, _ := json.Marshal(labels) - q := buildQuery{ - Tags: tag, - Labels: string(labelsJSON), - Dockerfile: "Dockerfile", - } - qs, err := query.Values(q) - if err != nil { - return err - } - - path := "/build" - url := fmt.Sprintf("%s%s?%s", api.endpoint, path, qs.Encode()) - req, err := http.NewRequest("POST", url, dockerBuildContext) - if err != nil { - return err - } - - req.Header.Set("Content-Type", "application/x-tar") - client := &http.Client{Timeout: 600 * time.Second} - resp, err := client.Do(req) - if err != nil { - return err - } - - defer resp.Body.Close() - scanner := bufio.NewScanner(resp.Body) - - for scanner.Scan() { - if os.Getenv("DEBUG") != "" { - log.Infof(scanner.Text()) - } - } - if err := scanner.Err(); err != nil { - return err - } - return nil -} +// import ( +// "fmt" +// "io/ioutil" +// "os" +// "path/filepath" +// +// "github.com/google/uuid" +// "github.com/metrue/fx/types" +// "github.com/metrue/fx/utils" +// ) +// +// func makeTar(project types.Project, tarFilePath string) error { +// dir, err := ioutil.TempDir("/tmp", "fx-build-dir") +// if err != nil { +// return err +// } +// +// defer os.RemoveAll(dir) +// +// for _, file := range project.Files { +// tmpfn := filepath.Join(dir, file.Path) +// if err := utils.EnsureFile(tmpfn); err != nil { +// return err +// } +// if err := ioutil.WriteFile(tmpfn, []byte(file.Body), 0666); err != nil { +// return err +// } +// } +// +// return utils.TarDir(dir, tarFilePath) +// } +// +// // Build build a project +// func (api *API) Build(project types.Project) (types.Service, error) { +// tarDir, err := ioutil.TempDir("/tmp", "fx-tar") +// if err != nil { +// return types.Service{}, err +// } +// defer os.RemoveAll(tarDir) +// +// imageID := uuid.New().String() +// tarFilePath := filepath.Join(tarDir, fmt.Sprintf("%s.tar", imageID)) +// if err := makeTar(project, tarFilePath); err != nil { +// return types.Service{}, err +// } +// labels := map[string]string{ +// "belong-to": "fx", +// } +// if err := api.BuildImage(tarFilePath, imageID, labels); err != nil { +// return types.Service{}, err +// } +// +// return types.Service{ +// Name: project.Name, +// Image: imageID, +// }, nil +// } diff --git a/container_runtimes/docker/http/service_build_test.go b/container_runtimes/docker/http/service_build_test.go index 443b025a..7fe37270 100644 --- a/container_runtimes/docker/http/service_build_test.go +++ b/container_runtimes/docker/http/service_build_test.go @@ -1,166 +1,82 @@ package api -import ( - "fmt" - "io/ioutil" - "net/http" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/metrue/fx/config" - "github.com/metrue/fx/constants" - "github.com/metrue/fx/types" - gock "gopkg.in/h2non/gock.v1" -) - -func TestMakeTar(t *testing.T) { - serviceName := "mock-service-abc" - project := types.Project{ - Name: serviceName, - Language: "node", - Files: []types.ProjectSourceFile{ - types.ProjectSourceFile{ - Path: "Dockerfile", - Body: ` -FROM metrue/fx-node-base - -COPY . . -EXPOSE 3000 -CMD ["node", "app.js"]`, - IsHandler: false, - }, - types.ProjectSourceFile{ - Path: "app.js", - Body: ` -const Koa = require('koa'); -const bodyParser = require('koa-bodyparser'); -const func = require('./fx'); - -const app = new Koa(); -app.use(bodyParser()); -app.use(ctx => { - const msg = func(ctx.request.body); - ctx.body = msg; -}); - -app.listen(3000);`, - IsHandler: false, - }, - types.ProjectSourceFile{ - Path: "fx.js", - Body: ` -module.exports = (input) => { - return input.a + input.b -} - `, - IsHandler: true, - }, - }, - } - tarDir, err := ioutil.TempDir("/tmp", "fx-tar") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tarDir) - - tarFilePath := filepath.Join(tarDir, fmt.Sprintf("%s.tar", serviceName)) - if err := makeTar(project, tarFilePath); err != nil { - t.Fatal(err) - } - - file, err := os.Open(tarFilePath) - if err != nil { - t.Fatal(err) - } - stat, err := file.Stat() - if err != nil { - t.Fatal(err) - } - if stat.Name() != serviceName+".tar" { - t.Fatalf("should get %s but got %s", serviceName+".tar", stat.Name()) - } - if stat.Size() <= 0 { - t.Fatalf("tarfile invalid: size %d", stat.Size()) - } -} - -func TestBuild(t *testing.T) { - defer gock.Off() - - host := config.Host{Host: "127.0.0.1"} - api, err := Create(host.Host, constants.AgentPort) - if err != nil { - t.Fatal(err) - } - - url := "http://" + host.Host + ":" + constants.AgentPort - gock.New(url). - Post("/v" + api.version + "/build"). - AddMatcher(func(req *http.Request, ereq *gock.Request) (bool, error) { - if strings.Contains(req.URL.String(), "/v"+api.version+"/build") { - return true, nil - } - return false, nil - }). - Reply(200). - JSON(map[string]string{ - "stream": "Step 1/5...", - }) - - serviceName := "mock-service-abc" - project := types.Project{ - Name: serviceName, - Language: "node", - Files: []types.ProjectSourceFile{ - types.ProjectSourceFile{ - Path: "Dockerfile", - Body: ` -FROM metrue/fx-node-base - -COPY . . -EXPOSE 3000 -CMD ["node", "app.js"]`, - IsHandler: false, - }, - types.ProjectSourceFile{ - Path: "app.js", - Body: ` -const Koa = require('koa'); -const bodyParser = require('koa-bodyparser'); -const func = require('./fx'); - -const app = new Koa(); -app.use(bodyParser()); -app.use(ctx => { - const msg = func(ctx.request.body); - ctx.body = msg; -}); - -app.listen(3000);`, - IsHandler: false, - }, - types.ProjectSourceFile{ - Path: "fx.js", - Body: ` -module.exports = (input) => { - return input.a + input.b -} - `, - IsHandler: true, - }, - }, - } - - service, err := api.Build(project) - if err != nil { - t.Fatal(err) - } - if service.Name != serviceName { - t.Fatalf("should get %s but got %s", serviceName, service.Name) - } - if service.Image == "" { - t.Fatal("service image should not be empty") - } -} +// import ( +// "fmt" +// "io/ioutil" +// "os" +// "path/filepath" +// "testing" +// +// "github.com/metrue/fx/types" +// ) +// +// func TestMakeTar(t *testing.T) { +// serviceName := "mock-service-abc" +// project := types.Project{ +// Name: serviceName, +// Language: "node", +// Files: []types.ProjectSourceFile{ +// types.ProjectSourceFile{ +// Path: "Dockerfile", +// Body: ` +// FROM metrue/fx-node-base +// +// COPY . . +// EXPOSE 3000 +// CMD ["node", "app.js"]`, +// IsHandler: false, +// }, +// types.ProjectSourceFile{ +// Path: "app.js", +// Body: ` +// const Koa = require('koa'); +// const bodyParser = require('koa-bodyparser'); +// const func = require('./fx'); +// +// const app = new Koa(); +// app.use(bodyParser()); +// app.use(ctx => { +// const msg = func(ctx.request.body); +// ctx.body = msg; +// }); +// +// app.listen(3000);`, +// IsHandler: false, +// }, +// types.ProjectSourceFile{ +// Path: "fx.js", +// Body: ` +// module.exports = (input) => { +// return input.a + input.b +// } +// `, +// IsHandler: true, +// }, +// }, +// } +// tarDir, err := ioutil.TempDir("/tmp", "fx-tar") +// if err != nil { +// t.Fatal(err) +// } +// defer os.RemoveAll(tarDir) +// +// tarFilePath := filepath.Join(tarDir, fmt.Sprintf("%s.tar", serviceName)) +// if err := makeTar(project, tarFilePath); err != nil { +// t.Fatal(err) +// } +// +// file, err := os.Open(tarFilePath) +// if err != nil { +// t.Fatal(err) +// } +// stat, err := file.Stat() +// if err != nil { +// t.Fatal(err) +// } +// if stat.Name() != serviceName+".tar" { +// t.Fatalf("should get %s but got %s", serviceName+".tar", stat.Name()) +// } +// if stat.Size() <= 0 { +// t.Fatalf("tarfile invalid: size %d", stat.Size()) +// } +// } diff --git a/container_runtimes/docker/http/service_run_test.go b/container_runtimes/docker/http/service_run_test.go deleted file mode 100644 index 60c6d414..00000000 --- a/container_runtimes/docker/http/service_run_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package api - -import ( - "net/http" - "testing" - - "github.com/golang/mock/gomock" - "github.com/metrue/fx/config" - "github.com/metrue/fx/constants" - "github.com/metrue/fx/types" - - gock "gopkg.in/h2non/gock.v1" -) - -func TestServiceRun(t *testing.T) { - defer gock.Off() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - host := config.Host{Host: "127.0.0.1"} - api, err := Create(host.Host, constants.AgentPort) - if err != nil { - t.Fatal(err) - } - - service := types.Service{ - Name: "a-mock-service", - Image: "a-mock-image-id", - } - - mockContainerID := "mock-container-id" - url := "http://" + host.Host + ":" + constants.AgentPort - gock.New(url). - Post("/v0.2.1/containers"). - AddMatcher(func(req *http.Request, ereq *gock.Request) (m bool, e error) { - // TODO multiple matching not supported by gock - if req.URL.String() == url+"/v0.2.1/containers/"+mockContainerID+"/start" { - return true, nil - } else if req.URL.String() == url+"/v0.2.1/containers/create?name="+service.Name { - return true, nil - } - - return false, nil - }). - Reply(201). - JSON(map[string]interface{}{ - "Id": mockContainerID, - "Warnings": []string{}, - }) - - // FIXME - if err := api.Run(9999, &service); err == nil { - t.Fatal(err) - } -} diff --git a/container_runtimes/docker/http/stop_test.go b/container_runtimes/docker/http/stop_test.go deleted file mode 100644 index 3b2095c1..00000000 --- a/container_runtimes/docker/http/stop_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package api - -import ( - "net/http" - "strings" - "testing" - - "github.com/golang/mock/gomock" - "github.com/metrue/fx/config" - "github.com/metrue/fx/constants" - gock "gopkg.in/h2non/gock.v1" -) - -func TestStop(t *testing.T) { - defer gock.Off() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - host := config.Host{Host: "127.0.0.1"} - api, err := Create(host.Host, constants.AgentPort) - if err != nil { - t.Fatal(err) - } - - mockServiceName := "mock-service-name" - url := "http://" + host.Host + ":" + constants.AgentPort - gock.New(url). - Post("/v" + api.version + "/containers/" + mockServiceName + "/stop"). - AddMatcher(func(req *http.Request, ereq *gock.Request) (m bool, e error) { - if strings.Contains(req.URL.String(), "/v"+api.version+"/containers/"+mockServiceName+"/stop") { - return true, nil - } - return false, nil - }). - Reply(204) - if err := api.Stop(mockServiceName); err != nil { - t.Fatal(err) - } -} diff --git a/container_runtimes/docker/http/up.go b/container_runtimes/docker/http/up.go index e798f48c..2b72f164 100644 --- a/container_runtimes/docker/http/up.go +++ b/container_runtimes/docker/http/up.go @@ -1,81 +1,71 @@ package api -import ( - "context" - "time" - - "github.com/apex/log" - "github.com/docker/docker/api/types/container" - "github.com/metrue/fx/constants" - "github.com/metrue/fx/types" -) - -// UpOptions options for up -type UpOptions struct { - Body []byte - Lang string - Name string - Port int - HealtCheck bool - Project types.Project -} - -// Up up a source code of function to be a service -func (api *API) Up(opt UpOptions) error { - service, err := api.Build(opt.Project) - if err != nil { - log.Fatalf("Build Service %s: %v", opt.Name, err) - return err - } - log.Infof("Build Service %s: %s", opt.Name, constants.CheckedSymbol) - - if err := api.Run(opt.Port, &service); err != nil { - log.Fatalf("Run Service: %v", err) - return err - } - log.Infof("Run Service: %s", constants.CheckedSymbol) - log.Infof("Service (%s) is running on: %s:%d", service.Name, service.Host, service.Port) - - if opt.HealtCheck { - go func() { - resultC, errC := api.ContainerWait( - context.Background(), - service.ID, - container.WaitConditionNextExit, - 20*time.Second, - ) - for { - select { - case res := <-resultC: - var msg string - if res.Error != nil { - msg = res.Error.Message - } - log.Warnf("container exited: Code(%d) %s %s", res.StatusCode, msg, constants.UncheckedSymbol) - case err := <-errC: - log.Fatalf("wait container status exit: %s, %v", constants.UncheckedSymbol, err) - } - } - }() - - trys := 0 - for { - if trys > 2 { - break - } - info, err := api.inspect(service.ID) - if err != nil { - log.Fatalf("healt checking failed: %v", err) - } - if info.State.Running { - log.Info("service is running") - } else { - log.Warnf("service is %s", info.State.Status) - } - time.Sleep(1 * time.Second) - trys++ - } - } - - return nil -} +// // UpOptions options for up +// type UpOptions struct { +// Body []byte +// Lang string +// Name string +// Port int +// HealtCheck bool +// Project types.Project +// } +// +// // Up up a source code of function to be a service +// func (api *API) Up(opt UpOptions) error { +// service, err := api.Build(opt.Project) +// if err != nil { +// log.Fatalf("Build Service %s: %v", opt.Name, err) +// return err +// } +// log.Infof("Build Service %s: %s", opt.Name, constants.CheckedSymbol) +// +// if err := api.Run(opt.Port, &service); err != nil { +// log.Fatalf("Run Service: %v", err) +// return err +// } +// log.Infof("Run Service: %s", constants.CheckedSymbol) +// log.Infof("Service (%s) is running on: %s:%d", service.Name, service.Host, service.Port) +// +// if opt.HealtCheck { +// go func() { +// resultC, errC := api.ContainerWait( +// context.Background(), +// service.ID, +// container.WaitConditionNextExit, +// 20*time.Second, +// ) +// for { +// select { +// case res := <-resultC: +// var msg string +// if res.Error != nil { +// msg = res.Error.Message +// } +// log.Warnf("container exited: Code(%d) %s %s", res.StatusCode, msg, constants.UncheckedSymbol) +// case err := <-errC: +// log.Fatalf("wait container status exit: %s, %v", constants.UncheckedSymbol, err) +// } +// } +// }() +// +// trys := 0 +// for { +// if trys > 2 { +// break +// } +// info, err := api.inspect(service.ID) +// if err != nil { +// log.Fatalf("healt checking failed: %v", err) +// } +// if info.State.Running { +// log.Info("service is running") +// } else { +// log.Warnf("service is %s", info.State.Status) +// } +// time.Sleep(1 * time.Second) +// trys++ +// } +// } +// +// return nil +// } diff --git a/container_runtimes/docker/sdk/docker.go b/container_runtimes/docker/sdk/docker.go index f6ffbd64..b52bf74d 100644 --- a/container_runtimes/docker/sdk/docker.go +++ b/container_runtimes/docker/sdk/docker.go @@ -9,10 +9,12 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" "github.com/apex/log" dockerTypes "github.com/docker/docker/api/types" dockerTypesContainer "github.com/docker/docker/api/types/container" + dockerFilters "github.com/docker/docker/api/types/filters" "github.com/docker/docker/client" "github.com/docker/go-connections/nat" "github.com/google/uuid" @@ -131,6 +133,11 @@ func (d *Docker) InspectImage(ctx context.Context, name string, img interface{}) return json.NewDecoder(rdr).Decode(&img) } +// TagImage tag image +func (d *Docker) TagImage(ctx context.Context, name string, tag string) error { + return d.ImageTag(ctx, name, tag) +} + // StartContainer create and start a container from given image func (d *Docker) StartContainer(ctx context.Context, name string, image string, ports []types.PortBinding) error { portSet := nat.PortSet{} @@ -183,6 +190,40 @@ func (d *Docker) InspectContainer(ctx context.Context, name string, container in return nil } +// ListContainer list containers +func (d *Docker) ListContainer(ctx context.Context, name string) ([]types.Service, error) { + args := dockerFilters.NewArgs( + dockerFilters.Arg("label", "belong-to=fx"), + ) + containers, err := d.ContainerList(ctx, dockerTypes.ContainerListOptions{ + Filters: args, + }) + if err != nil { + return []types.Service{}, err + } + + svs := make(map[string]types.Service) + for _, container := range containers { + // container name have extra forward slash + // https://github.com/moby/moby/issues/6705 + if strings.HasPrefix(container.Names[0], fmt.Sprintf("/%s", name)) { + svs[container.Image] = types.Service{ + Name: container.Names[0], + Image: container.Image, + ID: container.ID, + Host: container.Ports[0].IP, + Port: int(container.Ports[0].PublicPort), + State: container.State, + } + } + } + services := []types.Service{} + for _, s := range svs { + services = append(services, s) + } + return services, nil +} + var ( _ containerruntimes.ContainerRuntime = &Docker{} ) diff --git a/container_runtimes/runtimes.go b/container_runtimes/runtimes.go index 34488f3e..9f65ddf9 100644 --- a/container_runtimes/runtimes.go +++ b/container_runtimes/runtimes.go @@ -10,8 +10,10 @@ import ( type ContainerRuntime interface { BuildImage(ctx context.Context, workdir string, name string) error PushImage(ctx context.Context, name string) (string, error) - InspectImage(ct context.Context, name string, img interface{}) error + InspectImage(ctx context.Context, name string, img interface{}) error + TagImage(ctx context.Context, name string, tag string) error StartContainer(ctx context.Context, name string, image string, bindings []types.PortBinding) error StopContainer(ctx context.Context, name string) error InspectContainer(ctx context.Context, name string, container interface{}) error + ListContainer(ctx context.Context, filter string) ([]types.Service, error) } diff --git a/context/context.go b/context/context.go new file mode 100644 index 00000000..7f5f239c --- /dev/null +++ b/context/context.go @@ -0,0 +1,58 @@ +package context + +import ( + "context" + + "github.com/urfave/cli" +) + +type key string + +const ( + keyCliCtx = key("cmd_cli") +) + +// Context fx context +type Context struct { + context.Context +} + +// NewContext new a context +func NewContext() *Context { + ctx := context.Background() + return &Context{ctx} +} + +// FromCliContext create context from cli.Context +func FromCliContext(c *cli.Context) *Context { + ctx := NewContext() + ctx.WithCliContext(c) + return ctx +} + +// WithCliContext set cli.Context +func (ctx *Context) WithCliContext(c *cli.Context) { + newCtx := context.WithValue(ctx.Context, keyCliCtx, c) + ctx.Context = newCtx +} + +// GetCliContext get cli.Context +func (ctx *Context) GetCliContext() *cli.Context { + return ctx.Value(keyCliCtx).(*cli.Context) +} + +// Set a value with name +func (ctx *Context) Set(name string, value interface{}) { + newCtx := context.WithValue(ctx.Context, name, value) + ctx.Context = newCtx +} + +// Get a value +func (ctx *Context) Get(name string) interface{} { + return ctx.Context.Value(name) +} + +// Use invole a middle +func (ctx *Context) Use(fn func(ctx *Context) error) error { + return fn(ctx) +} diff --git a/context/context_test.go b/context/context_test.go new file mode 100644 index 00000000..e8879078 --- /dev/null +++ b/context/context_test.go @@ -0,0 +1,25 @@ +package context + +import ( + "testing" + + "github.com/urfave/cli" +) + +func TestContext(t *testing.T) { + ctx := NewContext() + cli := cli.NewContext(nil, nil, nil) + ctx.WithCliContext(cli) + c := ctx.GetCliContext() + if c != cli { + t.Fatalf("should get %v but got %v", cli, c) + } + + key := "k_1" + value := "hello" + ctx.Set(key, "hello") + v := ctx.Get(key).(string) + if v != value { + t.Fatalf("should get %v but %v", value, v) + } +} diff --git a/contrib/docker_packer/main.go b/contrib/docker_packer/main.go index 1245a599..8c40ff99 100644 --- a/contrib/docker_packer/main.go +++ b/contrib/docker_packer/main.go @@ -15,9 +15,6 @@ import ( "github.com/metrue/fx/utils" ) -// Version binary version -var Version = "0.0.1" - func init() { // TODO clean it up os.Setenv("DEBUG", "true") @@ -43,6 +40,7 @@ docker_packer } var tree map[string]string + //nolint if err := json.Unmarshal([]byte(str), &tree); err != nil { log.Fatalf("could not unmarshal meta: %s", meta) os.Exit(1) diff --git a/deploy/deployer.go b/deploy/deployer.go index 2abfb0e3..c16f6074 100644 --- a/deploy/deployer.go +++ b/deploy/deployer.go @@ -12,4 +12,5 @@ type Deployer interface { Destroy(ctx context.Context, name string) error Update(ctx context.Context, name string) error GetStatus(ctx context.Context, name string) error + List(ctx context.Context, name string) ([]types.Service, error) } diff --git a/deploy/docker/docker.go b/deploy/docker/docker.go index d24b7b38..8187f86f 100644 --- a/deploy/docker/docker.go +++ b/deploy/docker/docker.go @@ -8,55 +8,43 @@ import ( "time" dockerTypes "github.com/docker/docker/api/types" + "github.com/metrue/fx/constants" + containerruntimes "github.com/metrue/fx/container_runtimes" dockerHTTP "github.com/metrue/fx/container_runtimes/docker/http" - runtime "github.com/metrue/fx/container_runtimes/docker/sdk" + dockerSDK "github.com/metrue/fx/container_runtimes/docker/sdk" "github.com/metrue/fx/deploy" "github.com/metrue/fx/packer" "github.com/metrue/fx/types" "github.com/metrue/fx/utils" - "github.com/pkg/errors" ) // Docker manage container type Docker struct { - localClient *runtime.Docker + cli containerruntimes.ContainerRuntime } // CreateClient create a docker instance -func CreateClient(ctx context.Context) (*Docker, error) { - cli, err := runtime.CreateClient(ctx) - if err != nil { - return nil, err +func CreateClient(ctx context.Context) (d *Docker, err error) { + var cli containerruntimes.ContainerRuntime + host := os.Getenv("DOCKER_REMOTE_HOST_ADDR") + user := os.Getenv("DOCKER_REMOTE_HOST_USER") + if host != "" && user != "" { + cli, err = dockerHTTP.Create(host, constants.AgentPort) + if err != nil { + return nil, err + } + } else { + cli, err = dockerSDK.CreateClient(ctx) + if err != nil { + return nil, err + } } - return &Docker{localClient: cli}, nil + + return &Docker{cli: cli}, nil } // Deploy create a Docker container from given image, and bind the constants.FxContainerExposePort to given port func (d *Docker) Deploy(ctx context.Context, fn types.Func, name string, ports []types.PortBinding) error { - // if DOCKER_REMOTE_HOST and DOCKER_REMOTE_PORT given - // it means user is going to deploy service to remote host - host := os.Getenv("DOCKER_REMOTE_HOST") - port := os.Getenv("DOCKER_REMOTE_PORT") - if port != "" && host != "" { - httpClient, err := dockerHTTP.Create(host, port) - if err != nil { - return err - } - - project, err := packer.Pack(name, fn) - if err != nil { - return errors.Wrapf(err, "could pack function %v (%s)", name, fn) - } - return httpClient.Up(dockerHTTP.UpOptions{ - Body: []byte(fn.Source), - Lang: fn.Language, - Name: name, - Port: int(ports[0].ServiceBindingPort), - HealtCheck: false, - Project: project, - }) - } - workdir := fmt.Sprintf("/tmp/fx-%d", time.Now().Unix()) defer os.RemoveAll(workdir) @@ -64,13 +52,14 @@ func (d *Docker) Deploy(ctx context.Context, fn types.Func, name string, ports [ log.Fatalf("could not pack function %v: %v", fn, err) return err } - if err := d.localClient.BuildImage(ctx, workdir, name); err != nil { + + if err := d.cli.BuildImage(ctx, workdir, name); err != nil { log.Fatalf("could not build image: %v", err) return err } nameWithTag := name + ":latest" - if err := d.localClient.ImageTag(ctx, name, nameWithTag); err != nil { + if err := d.cli.TagImage(ctx, name, nameWithTag); err != nil { log.Fatalf("could not tag image: %v", err) return err } @@ -81,12 +70,12 @@ func (d *Docker) Deploy(ctx context.Context, fn types.Func, name string, ports [ // But it takes some times waiting image ready after image built, we retry to make sure it ready here var imgInfo dockerTypes.ImageInspect if err := utils.RunWithRetry(func() error { - return d.localClient.InspectImage(ctx, name, &imgInfo) + return d.cli.InspectImage(ctx, name, &imgInfo) }, time.Second*1, 5); err != nil { return err } - return d.localClient.StartContainer(ctx, name, name, ports) + return d.cli.StartContainer(ctx, name, name, ports) } // Update a container @@ -96,7 +85,7 @@ func (d *Docker) Update(ctx context.Context, name string) error { // Destroy stop and remove container func (d *Docker) Destroy(ctx context.Context, name string) error { - return d.localClient.ContainerStop(ctx, name, nil) + return d.cli.StopContainer(ctx, name) } // GetStatus get status of container @@ -104,6 +93,12 @@ func (d *Docker) GetStatus(ctx context.Context, name string) error { return nil } +// List services +func (d *Docker) List(ctx context.Context, name string) ([]types.Service, error) { + // FIXME support remote host + return d.cli.ListContainer(ctx, name) +} + var ( _ deploy.Deployer = &Docker{} ) diff --git a/deploy/kubernetes/kubernetes.go b/deploy/kubernetes/kubernetes.go index 1ad6dbb5..6f17d94d 100644 --- a/deploy/kubernetes/kubernetes.go +++ b/deploy/kubernetes/kubernetes.go @@ -131,6 +131,11 @@ func (k *K8S) GetStatus(ctx context.Context, name string) error { return nil } +// List services +func (k *K8S) List(ctx context.Context, name string) ([]types.Service, error) { + return []types.Service{}, nil +} + var ( _ deploy.Deployer = &K8S{} ) diff --git a/docs/lightsail.yml b/docs/lightsail.yml new file mode 100644 index 00000000..4ab28d6f --- /dev/null +++ b/docs/lightsail.yml @@ -0,0 +1,16 @@ +# fx on Amazon Lightsai + +* make sure your instance have docker installed and running, +* make sure your instance can be ssh login (with user and password) + +``` +ssh @ +``` + +* make sure your instance accept port 8866 + +* then you can deploy function to remote host + +``` +DOCKER_REMOTE_HOST_ADDR= DOCKER_REMOTE_HOST_USER= DOCKER_REMOTE_HOST_PASSWORD= ./build/fx up -p 1234 test/functions/func.js +``` diff --git a/doctor/doctor.go b/doctor/doctor.go index e0027fcc..e446910f 100644 --- a/doctor/doctor.go +++ b/doctor/doctor.go @@ -2,7 +2,6 @@ package doctor import ( "github.com/apex/log" - "github.com/metrue/fx/config" "github.com/metrue/fx/constants" "github.com/metrue/fx/pkg/command" "github.com/metrue/go-ssh-client" @@ -10,16 +9,23 @@ import ( // Doctor health checking type Doctor struct { - host config.Host + host string sshClient ssh.Client } +func isLocal(host string) bool { + if host == "" { + return false + } + return host == "127.0.0.1" || host == "localhost" || host == "0.0.0.0" +} + // New a doctor -func New(host config.Host) *Doctor { - sshClient := ssh.New(host.Host). - WithUser(host.User). - WithPassword(host.Password) +func New(host, user, password string) *Doctor { + sshClient := ssh.New(host). + WithUser(user). + WithPassword(password) return &Doctor{ host: host, sshClient: sshClient, @@ -32,7 +38,7 @@ func (d *Doctor) Start() error { checkAgent := "docker inspect " + constants.AgentContainerName cmds := []*command.Command{} - if d.host.IsRemote() { + if !isLocal(d.host) { cmds = append(cmds, command.New("check if dockerd is running", checkDocker, command.NewRemoteRunner(d.sshClient)), command.New("check if fx agent is running", checkAgent, command.NewRemoteRunner(d.sshClient)), diff --git a/fx.go b/fx.go index 640eac3a..99091ab2 100644 --- a/fx.go +++ b/fx.go @@ -5,29 +5,20 @@ import ( "fmt" "net/http" "os" - "path" "regexp" "github.com/apex/log" "github.com/google/uuid" - "github.com/metrue/fx/config" + "github.com/metrue/fx/context" "github.com/metrue/fx/handlers" + "github.com/metrue/fx/middlewares" "github.com/urfave/cli" ) const version = "0.8.0" -var cfg *config.Config - func init() { go checkForUpdate() - configDir := path.Join(os.Getenv("HOME"), ".fx") - cfg := config.New(configDir) - - if err := cfg.Init(); err != nil { - log.Fatalf("Init config failed %s", err) - os.Exit(1) - } } func checkForUpdate() { @@ -67,63 +58,10 @@ func main() { app.Commands = []cli.Command{ { - Name: "infra", - Usage: "manage infrastructure of fx", - Subcommands: []cli.Command{ - { - Name: "add", - Usage: "add a new machine", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "name, N", - Usage: "a alias name for this machine", - }, - cli.StringFlag{ - Name: "host, H", - Usage: "host name or IP address of a machine", - }, - cli.StringFlag{ - Name: "user, U", - Usage: "user name required for SSH login", - }, - cli.StringFlag{ - Name: "password, P", - Usage: "password required for SSH login", - }, - }, - Action: func(c *cli.Context) error { - return handlers.AddHost(cfg)(c) - }, - }, - { - Name: "remove", - Usage: "remove an existing machine", - Action: func(c *cli.Context) error { - return handlers.RemoveHost(cfg)(c) - }, - }, - { - Name: "list", - Aliases: []string{"ls"}, - Usage: "list machines", - Action: func(c *cli.Context) error { - return handlers.ListHosts(cfg)(c) - }, - }, - { - Name: "activate", - Usage: "enable a machine be a host of fx infrastructure", - Action: func(c *cli.Context) error { - return handlers.Activate(cfg)(c) - }, - }, - { - Name: "deactivate", - Usage: "disable a machine be a host of fx infrastructure", - Action: func(c *cli.Context) error { - return handlers.Deactivate(cfg)(c) - }, - }, + Name: "init", + Usage: "start fx agent on host", + Action: func(c *cli.Context) error { + return handlers.Init()(context.FromCliContext(c)) }, }, { @@ -140,7 +78,11 @@ func main() { }, }, Action: func(c *cli.Context) error { - return handlers.BuildImage(cfg)(c) + ctx := context.FromCliContext(c) + if err := ctx.Use(middlewares.Setup); err != nil { + log.Fatalf("%v", err) + } + return handlers.BuildImage()(ctx) }, }, { @@ -153,7 +95,7 @@ func main() { }, }, Action: func(c *cli.Context) error { - return handlers.ExportImage()(c) + return handlers.ExportImage()(context.FromCliContext(c)) }, }, }, @@ -162,7 +104,7 @@ func main() { Name: "doctor", Usage: "health check for fx", Action: func(c *cli.Context) error { - return handlers.Doctor(cfg)(c) + return handlers.Doctor()(context.FromCliContext(c)) }, }, { @@ -189,7 +131,14 @@ func main() { }, }, Action: func(c *cli.Context) error { - return handlers.Up(cfg)(c) + ctx := context.FromCliContext(c) + if err := ctx.Use(middlewares.Setup); err != nil { + log.Fatalf("%v", err) + } + if err := ctx.Use(middlewares.Binding); err != nil { + log.Fatalf("%v", err) + } + return handlers.Up()(ctx) }, }, { @@ -197,7 +146,11 @@ func main() { Usage: "destroy a service", ArgsUsage: "[service 1, service 2, ....]", Action: func(c *cli.Context) error { - return handlers.Down(cfg)(c) + ctx := context.FromCliContext(c) + if err := ctx.Use(middlewares.Setup); err != nil { + log.Fatalf("%v", err) + } + return handlers.Down()(ctx) }, }, { @@ -205,7 +158,11 @@ func main() { Aliases: []string{"ls"}, Usage: "list deployed services", Action: func(c *cli.Context) error { - return handlers.List(cfg)(c) + ctx := context.FromCliContext(c) + if err := ctx.Use(middlewares.Setup); err != nil { + log.Fatalf("%v", err) + } + return handlers.List()(ctx) }, }, { @@ -218,7 +175,7 @@ func main() { }, }, Action: func(c *cli.Context) error { - return handlers.Call(cfg)(c) + return handlers.Call()(context.FromCliContext(c)) }, }, } diff --git a/go.mod b/go.mod index 51e39661..beccb7ed 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,6 @@ require ( github.com/googleapis/gnostic v0.3.1 // indirect github.com/gorilla/mux v1.7.3 // indirect github.com/imdario/mergo v0.3.7 // indirect - github.com/magiconair/properties v1.8.1 // indirect github.com/metrue/go-ssh-client v0.0.0-20190810064746-98a7a27048f3 github.com/mholt/archiver v3.1.1+incompatible github.com/morikuni/aec v1.0.0 // indirect diff --git a/hack/install_docker.sh b/hack/install_docker.sh new file mode 100755 index 00000000..dc30e8a6 --- /dev/null +++ b/hack/install_docker.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -e +# ++ +# verified on Ubuntu 16.04 x64 +# ++ +user_host=$1 + +ssh ${user_host} 'bash -s' <") - return nil - } - return cfg.RemoveHost(name) - } -} - -// ListHosts list hosts -func ListHosts(cfg config.Configer) HandleFunc { - return func(ctx *cli.Context) error { - hosts, err := cfg.ListMachines() - if err != nil { - return err - } - - return utils.OutputJSON(hosts) - } -} diff --git a/handlers/image.go b/handlers/image.go index 07e72d94..190b8ea4 100644 --- a/handlers/image.go +++ b/handlers/image.go @@ -4,29 +4,32 @@ import ( "fmt" "io/ioutil" "os" + "time" "github.com/apex/log" "github.com/google/uuid" - "github.com/metrue/fx/config" "github.com/metrue/fx/constants" - api "github.com/metrue/fx/container_runtimes/docker/http" + containerruntimes "github.com/metrue/fx/container_runtimes" + "github.com/metrue/fx/context" "github.com/metrue/fx/packer" - "github.com/metrue/fx/provision" "github.com/metrue/fx/types" "github.com/metrue/fx/utils" "github.com/pkg/errors" - "github.com/urfave/cli" ) // BuildImage build image -func BuildImage(cfg config.Configer) HandleFunc { - return func(ctx *cli.Context) error { - funcFile := ctx.Args().First() - tag := ctx.String("tag") +func BuildImage() HandleFunc { + return func(ctx *context.Context) error { + cli := ctx.GetCliContext() + funcFile := cli.Args().First() + tag := cli.String("tag") if tag == "" { tag = uuid.New().String() } + workdir := fmt.Sprintf("/tmp/fx-%d", time.Now().Unix()) + defer os.RemoveAll(workdir) + body, err := ioutil.ReadFile(funcFile) if err != nil { log.Fatalf("function code load failed: %v", err) @@ -34,57 +37,33 @@ func BuildImage(cfg config.Configer) HandleFunc { } log.Infof("function code loaded: %v", constants.CheckedSymbol) lang := utils.GetLangFromFileName(funcFile) - pwd, err := os.Getwd() - if err != nil { - log.Fatalf("could not get current work directory: %v", err) + + fn := types.Func{Language: lang, Source: string(body)} + + if err := packer.PackIntoDir(fn, workdir); err != nil { + log.Fatalf("could not pack function %v: %v", fn, err) return err } - tarFile := fmt.Sprintf("%s.%s.tar", pwd, tag) - defer os.RemoveAll(tarFile) - if err := packer.PackIntoTar(types.Func{Language: lang, Source: string(body)}, tarFile); err != nil { - log.Fatalf("could not pack function: %v", err) - return err - } - log.Infof("function packed: %v", constants.CheckedSymbol) - - hosts, err := cfg.ListActiveMachines() - if err != nil { - log.Fatalf("could not list active machine: %v", err) - return errors.Wrap(err, "list active machines failed") - } - - if len(hosts) == 0 { - log.Warnf("no active machines") - return nil - } - for n, host := range hosts { - if !host.Provisioned { - provisionor := provision.New(host) - if err := provisionor.Start(); err != nil { - return errors.Wrapf(err, "could not provision %s", n) - } - log.Infof("provision machine %v: %s", n, constants.CheckedSymbol) - if err := cfg.UpdateProvisionedStatus(n, true); err != nil { - return errors.Wrap(err, "update machine provision status failed") - } - } - - if err := api.MustCreate(host.Host, constants.AgentPort). - BuildImage(tarFile, tag, map[string]string{}); err != nil { + docker, ok := ctx.Get("docker").(containerruntimes.ContainerRuntime) + if ok { + nameWithTag := tag + ":latest" + if err := docker.BuildImage(ctx.Context, workdir, nameWithTag); err != nil { return err } - log.Infof("image built on machine %s: %v", n, constants.CheckedSymbol) + log.Infof("image built: %v", constants.CheckedSymbol) + return nil } - return nil + return fmt.Errorf("no available docker cli") } } // ExportImage export service's code into a directory func ExportImage() HandleFunc { - return func(ctx *cli.Context) (err error) { - funcFile := ctx.Args().First() - outputDir := ctx.String("output") + return func(ctx *context.Context) (err error) { + cli := ctx.GetCliContext() + funcFile := cli.Args().First() + outputDir := cli.String("output") if outputDir == "" { log.Fatalf("output directory required") return nil diff --git a/handlers/infra_activate.go b/handlers/infra_activate.go deleted file mode 100644 index f8d268ef..00000000 --- a/handlers/infra_activate.go +++ /dev/null @@ -1,46 +0,0 @@ -package handlers - -import ( - "github.com/apex/log" - "github.com/metrue/fx/config" - "github.com/metrue/fx/constants" - "github.com/metrue/fx/provision" - "github.com/urfave/cli" -) - -// Activate a machine to be fx server -func Activate(cfg config.Configer) HandleFunc { - return func(ctx *cli.Context) error { - name := ctx.Args().First() - if name == "" { - log.Fatalf("name required for: fx infra activate ") - return nil - } - - host, err := cfg.GetMachine(name) - if err != nil { - log.Fatalf("could get host %v, make sure you add it first", err) - log.Info("You can add a machine by: \n fx infra add -Name -H -U -P ") - return nil - } - if !host.Provisioned { - provisionor := provision.New(host) - if err := provisionor.Start(); err != nil { - log.Fatalf("could not provision %s: %v", name, err) - return nil - } - log.Infof("provision machine %v: %s", name, constants.CheckedSymbol) - if err := cfg.UpdateProvisionedStatus(name, true); err != nil { - log.Fatalf("update machine provision status failed: %v", err) - } - } - - if err := cfg.EnableMachine(name); err != nil { - log.Fatalf("could not enable %s: %v", name, err) - return nil - } - log.Infof("enble machine %v: %s", name, constants.CheckedSymbol) - - return nil - } -} diff --git a/handlers/infra_deactivate.go b/handlers/infra_deactivate.go deleted file mode 100644 index 1c702959..00000000 --- a/handlers/infra_deactivate.go +++ /dev/null @@ -1,25 +0,0 @@ -package handlers - -import ( - "github.com/apex/log" - "github.com/metrue/fx/config" - "github.com/metrue/fx/constants" - "github.com/urfave/cli" -) - -// Deactivate a machine -func Deactivate(cfg config.Configer) HandleFunc { - return func(ctx *cli.Context) error { - name := ctx.Args().First() - if name == "" { - log.Fatalf("name required for: fx infra activate ") - return nil - } - if err := cfg.DisableMachine(name); err != nil { - log.Fatalf("could not disable %s: %v", name, err) - return nil - } - log.Infof("machine %s deactive: %v", name, constants.CheckedSymbol) - return nil - } -} diff --git a/handlers/init.go b/handlers/init.go new file mode 100644 index 00000000..93e9ceb0 --- /dev/null +++ b/handlers/init.go @@ -0,0 +1,31 @@ +package handlers + +import ( + "os" + + "github.com/apex/log" + "github.com/metrue/fx/context" + "github.com/metrue/fx/provision" +) + +// Init start fx-agent +func Init() HandleFunc { + return func(ctx *context.Context) error { + host := os.Getenv("DOCKER_REMOTE_HOST_ADDR") + user := os.Getenv("DOCKER_REMOTE_HOST_USER") + passord := os.Getenv("DOCKER_REMOTE_HOST_PASSWORD") + if host == "" { + host = "127.0.0.1" + } + provisioner := provision.NewWithHost(host, user, passord) + if !provisioner.IsFxAgentRunning() { + if err := provisioner.StartFxAgent(); err != nil { + log.Fatalf("could not start fx agent on host: %s", err) + return err + } + log.Info("fx agent started") + } + log.Info("fx agent already started") + return nil + } +} diff --git a/handlers/list.go b/handlers/list.go index c74e9c5c..c3c4d8e1 100644 --- a/handlers/list.go +++ b/handlers/list.go @@ -1,25 +1,28 @@ package handlers import ( - "github.com/apex/log" - "github.com/metrue/fx/config" - "github.com/metrue/fx/constants" - api "github.com/metrue/fx/container_runtimes/docker/http" - "github.com/urfave/cli" + "github.com/metrue/fx/context" + "github.com/metrue/fx/deploy" + "github.com/metrue/fx/utils" ) // List command handle -func List(cfg config.Configer) HandleFunc { - return func(ctx *cli.Context) error { - hosts, err := cfg.ListActiveMachines() +func List() HandleFunc { + return func(ctx *context.Context) error { + cli := ctx.GetCliContext() + deployer := ctx.Get("deployer").(deploy.Deployer) + + services, err := deployer.List(ctx.Context, cli.Args().First()) if err != nil { - log.Fatalf("list active machines failed: %v", err) + return err } - for name, host := range hosts { - if err := api.MustCreate(host.Host, constants.AgentPort).List(ctx.Args().First()); err != nil { - log.Fatalf("list functions on machine %s failed: %v", name, err) + + for _, service := range services { + if err := utils.OutputJSON(service); err != nil { + return err } } + return nil } } diff --git a/handlers/up.go b/handlers/up.go index 211a09ba..80535600 100644 --- a/handlers/up.go +++ b/handlers/up.go @@ -1,21 +1,15 @@ package handlers import ( - "context" "fmt" "io/ioutil" - "os" "github.com/apex/log" - "github.com/metrue/fx/config" - "github.com/metrue/fx/constants" + "github.com/metrue/fx/context" "github.com/metrue/fx/deploy" - dockerDeployer "github.com/metrue/fx/deploy/docker" - k8sDeployer "github.com/metrue/fx/deploy/kubernetes" "github.com/metrue/fx/types" "github.com/metrue/fx/utils" "github.com/pkg/errors" - "github.com/urfave/cli" ) // PortRange usable port range https: //en.wikipedia.org/wiki/Ephemeral_port @@ -28,11 +22,12 @@ var PortRange = struct { } // Up command handle -func Up(cfg config.Configer) HandleFunc { - return func(ctx *cli.Context) (err error) { - funcFile := ctx.Args().First() - name := ctx.String("name") - port := ctx.Int("port") +func Up() HandleFunc { + return func(ctx *context.Context) (err error) { + cli := ctx.GetCliContext() + funcFile := cli.Args().First() + name := cli.String("name") + port := cli.Int("port") defer func() { if r := recover(); r != nil { @@ -54,38 +49,10 @@ func Up(cfg config.Configer) HandleFunc { return errors.Wrap(err, "read source failed") } lang := utils.GetLangFromFileName(funcFile) - var deployer deploy.Deployer - var bindings []types.PortBinding - if os.Getenv("KUBECONFIG") != "" { - deployer, err = k8sDeployer.Create() - if err != nil { - return err - } - bindings = []types.PortBinding{ - types.PortBinding{ - ServiceBindingPort: 80, - ContainerExposePort: constants.FxContainerExposePort, - }, - types.PortBinding{ - ServiceBindingPort: 443, - ContainerExposePort: constants.FxContainerExposePort, - }, - } - } else { - bctx := context.Background() - deployer, err = dockerDeployer.CreateClient(bctx) - if err != nil { - return err - } - bindings = []types.PortBinding{ - types.PortBinding{ - ServiceBindingPort: int32(port), - ContainerExposePort: constants.FxContainerExposePort, - }, - } - } + deployer := ctx.Get("deployer").(deploy.Deployer) + bindings := ctx.Get("bindings").([]types.PortBinding) return deployer.Deploy( - context.Background(), + ctx.Context, types.Func{Language: lang, Source: string(body)}, name, bindings, diff --git a/middlewares/binding.go b/middlewares/binding.go new file mode 100644 index 00000000..8304d8cc --- /dev/null +++ b/middlewares/binding.go @@ -0,0 +1,38 @@ +package middlewares + +import ( + "os" + + "github.com/metrue/fx/constants" + "github.com/metrue/fx/context" + "github.com/metrue/fx/types" +) + +// Binding create bindings +func Binding(ctx *context.Context) error { + cli := ctx.GetCliContext() + port := cli.Int("port") + + var bindings []types.PortBinding + if os.Getenv("KUBECONFIG") != "" { + bindings = []types.PortBinding{ + types.PortBinding{ + ServiceBindingPort: 80, + ContainerExposePort: constants.FxContainerExposePort, + }, + types.PortBinding{ + ServiceBindingPort: 443, + ContainerExposePort: constants.FxContainerExposePort, + }, + } + } else { + bindings = []types.PortBinding{ + types.PortBinding{ + ServiceBindingPort: int32(port), + ContainerExposePort: constants.FxContainerExposePort, + }, + } + } + ctx.Set("bindings", bindings) + return nil +} diff --git a/middlewares/setup.go b/middlewares/setup.go new file mode 100644 index 00000000..7d625550 --- /dev/null +++ b/middlewares/setup.go @@ -0,0 +1,49 @@ +package middlewares + +import ( + "os" + + "github.com/metrue/fx/constants" + containerruntimes "github.com/metrue/fx/container_runtimes" + dockerHTTP "github.com/metrue/fx/container_runtimes/docker/http" + dockerSDK "github.com/metrue/fx/container_runtimes/docker/sdk" + "github.com/metrue/fx/context" + "github.com/metrue/fx/deploy" + dockerDeployer "github.com/metrue/fx/deploy/docker" + k8sDeployer "github.com/metrue/fx/deploy/kubernetes" +) + +// Setup create k8s or docker cli +func Setup(ctx *context.Context) (err error) { + var deployer deploy.Deployer + if os.Getenv("KUBECONFIG") != "" { + deployer, err = k8sDeployer.Create() + if err != nil { + return err + } + } else { + deployer, err = dockerDeployer.CreateClient(ctx.Context) + if err != nil { + return err + } + } + ctx.Set("deployer", deployer) + + host := os.Getenv("DOCKER_REMOTE_HOST_ADDR") + user := os.Getenv("DOCKER_REMOTE_HOST_USER") + var docker containerruntimes.ContainerRuntime + if host != "" && user != "" { + docker, err = dockerHTTP.Create(host, constants.AgentPort) + if err != nil { + return err + } + } else { + docker, err = dockerSDK.CreateClient(ctx) + if err != nil { + return err + } + } + ctx.Set("docker", docker) + + return nil +} diff --git a/packer/a_packer-packr.go b/packer/a_packer-packr.go index 9ce288b2..2d8cdc71 100644 --- a/packer/a_packer-packr.go +++ b/packer/a_packer-packr.go @@ -11,7 +11,7 @@ func init() { packr.PackJSONBytes("./images", "d/app.d", "\"aW1wb3J0IHN0ZC5qc29uOwppbXBvcnQgYXJzZC5jZ2k7CmltcG9ydCBmeDsgCgp2b2lkIGhhbmRsZShDZ2kgY2dpKSAKewogICAgaWYgKGNnaS5yZXF1ZXN0TWV0aG9kID09IENnaS5SZXF1ZXN0TWV0aG9kLlBPU1QgJiYgY2dpLnBhdGhJbmZvID09ICIvIikKICAgIHsKICAgICAgICBhdXRvIGlucHV0ID0gcGFyc2VKU09OKGNnaS5wb3N0SnNvbik7CiAgICAgICAgYXV0byByZXN1bHQgPSBKU09OVmFsdWUoZXhlY3V0ZUZ4KGlucHV0KSk7CiAgICAgICAgY2dpLnNldFJlc3BvbnNlQ29udGVudFR5cGUoImFwcGxpY2F0aW9uL2pzb24iKTsKICAgICAgICBjZ2kud3JpdGUodG9KU09OKHJlc3VsdCkpOwogICAgfQp9CgptaXhpbiBHZW5lcmljTWFpbiFoYW5kbGU7\"") packr.PackJSONBytes("./images", "d/arsd/cgi.d", "\"Ly8gRklYTUU6IGlmIGFuIGV4Y2VwdGlvbiBpcyB0aHJvd24sIHdlIHNob3VsZG4ndCBuZWNlc3NhcmlseSBjYWNoZS4uLgovLyBGSVhNRTogdGhlcmUncyBzb21lIGFubm95aW5nIGR1cGxpY2F0aW9uIG9mIGNvZGUgaW4gdGhlIHZhcmlvdXMgdmVyc2lvbmVkIG1haW5zCgovLyBOb3RlOiBzcGF3bi1mY2dpIGNhbiBoZWxwIHdpdGggZmFzdGNnaSBvbiBuZ2lueAoKLy8gRklYTUU6IHRvIGRvOiBhZGQgb3BlbnNzbCBvcHRpb25hbGx5Ci8vIG1ha2Ugc3VyZSBlbWJlZGRlZF9odHRwZCBkb2Vzbid0IHNlbmQgdHdvIGFuc3dlcnMgaWYgb25lIHdyaXRlcygpIHRoZW4gZGllcwoKLysrCglQcm92aWRlcyBhIHVuaWZvcm0gc2VydmVyLXNpZGUgQVBJIGZvciBDR0ksIEZhc3RDR0ksIFNDR0ksIGFuZCBIVFRQIHdlYiBhcHBsaWNhdGlvbnMuCgoJLS0tCglpbXBvcnQgYXJzZC5jZ2k7CgoJLy8gSW5zdGVhZCBvZiB3cml0aW5nIHlvdXIgb3duIG1haW4oKSwgeW91IHNob3VsZCB3cml0ZSBhIGZ1bmN0aW9uCgkvLyB0aGF0IHRha2VzIGEgQ2dpIHBhcmFtLCBhbmQgdXNlIG1peGluIEdlbmVyaWNNYWluCgkvLyBmb3IgbWF4aW11bSBjb21wYXRpYmlsaXR5IHdpdGggZGlmZmVyZW50IHdlYiBzZXJ2ZXJzLgoJdm9pZCBoZWxsbyhDZ2kgY2dpKSB7CgkJY2dpLnNldFJlc3BvbnNlQ29udGVudFR5cGUoInRleHQvcGxhaW4iKTsKCgkJaWYoIm5hbWUiIGluIGNnaS5nZXQpCgkJCWNnaS53cml0ZSgiSGVsbG8sICIgfiBjZ2kuZ2V0WyJuYW1lIl0pOwoJCWVsc2UKCQkJY2dpLndyaXRlKCJIZWxsbywgd29ybGQhIik7Cgl9CgoJbWl4aW4gR2VuZXJpY01haW4haGVsbG87CgktLS0KCgoJQ29tcGlsZV9hbmRfcnVuOgoJCglGb3IgQ0dJLCBgZG1kIHlvdXJmaWxlLmQgY2dpLmRgIHRoZW4gcHV0IHRoZSBleGVjdXRhYmxlIGluIHlvdXIgY2dpLWJpbiBkaXJlY3RvcnkuCgoJRm9yIEZhc3RDR0k6IGBkbWQgeW91cmZpbGUuZCBjZ2kuZCAtdmVyc2lvbj1mYXN0Y2dpYCBhbmQgcnVuIGl0LiBzcGF3bi1mY2dpIGhlbHBzIG9uIG5naW54LiBZb3UgY2FuIHB1dCB0aGUgZmlsZSBpbiB0aGUgZGlyZWN0b3J5IGZvciBBcGFjaGUuIE9uIElJUywgcnVuIGl0IHdpdGggYSBwb3J0IG9uIHRoZSBjb21tYW5kIGxpbmUuCgoJRm9yIFNDR0k6IGBkbWQgeW91cmZpbGUuZCBjZ2kuZCAtdmVyc2lvbj1zY2dpYCBhbmQgcnVuIHRoZSBleGVjdXRhYmxlLCBwcm92aWRpbmcgYSBwb3J0IG51bWJlciBvbiB0aGUgY29tbWFuZCBsaW5lLgoKCUZvciBhbiBlbWJlZGRlZCBIVFRQIHNlcnZlciwgcnVuIGBkbWQgeW91cmZpbGUuZCBjZ2kuZCAtdmVyc2lvbj1lbWJlZGRlZF9odHRwZGAgYW5kIHJ1biB0aGUgZ2VuZXJhdGVkIHByb2dyYW0uIEl0IGxpc3RlbnMgb24gcG9ydCA4MDg1IGJ5IGRlZmF1bHQuIFlvdSBjYW4gY2hhbmdlIHRoaXMgb24gdGhlIGNvbW1hbmQgbGluZSB3aXRoIHRoZSAtLXBvcnQgb3B0aW9uIHdoZW4gcnVubmluZyB5b3VyIHByb2dyYW0uCgoJWW91IGNhbiBhbHNvIHNpbXVsYXRlIGEgcmVxdWVzdCBieSBwYXNzaW5nIHBhcmFtZXRlcnMgb24gdGhlIGNvbW1hbmQgbGluZSwgbGlrZToKCgkkKENPTlNPTEUKCS4veW91cnByb2dyYW0gR0VUIC8gbmFtZT1hZHIKCSkKCglBbmQgaXQgd2lsbCBwcmludCB0aGUgcmVzdWx0IHRvIHN0ZG91dC4KCglDR0lfU2V0dXBfdGlwczoKCglPbiBBcGFjaGUsIHlvdSBtYXkgZG8gYFNldEhhbmRsZXIgY2dpLXNjcmlwdGAgaW4geW91ciBgLmh0YWNjZXNzYCBmaWxlLgoKCUludGVncmF0aW9uX3RpcHM6CgoJY2dpLmQgd29ya3Mgd2VsbCB3aXRoIGRvbS5kIGZvciBnZW5lcmF0aW5nIGh0bWwuIFlvdSBtYXkgYWxzbyB1c2Ugd2ViLmQgZm9yIG90aGVyIHV0aWxpdGllcyBhbmQgYXV0b21hdGljIGFwaSB3cmFwcGluZy4KCglkb20uZCB1c2FnZToKCgktLS0KCQlpbXBvcnQgYXJzZC5jZ2k7CgkJaW1wb3J0IGFyc2QuZG9tOwoKCQl2b2lkIGhlbGxvX2RvbShDZ2kgY2dpKSB7CgkJCWF1dG8gZG9jdW1lbnQgPSBuZXcgRG9jdW1lbnQoKTsKCgkJCXN0YXRpYyBpbXBvcnQgc3RkLmZpbGU7CgkJCS8vIHBhcnNlIHRoZSBmaWxlIGluIHN0cmljdCBtb2RlLCByZXF1aXJpbmcgaXQgdG8gYmUgd2VsbC1mb3JtZWQgVVRGLTggWEhUTUwKCQkJLy8gKFlvdSdsbCBhcHByZWNpYXRlIHRoaXMgaWYgeW91J3ZlIGV2ZXIgaGFkIHRvIGRlYWwgd2l0aCBhIG1pc3NpbmcgPC9kaXY+CgkJCS8vIG9yIHNvbWV0aGluZyBpbiBhIHBocCBvciBlcmIgdGVtcGxhdGUgYmVmb3JlIHRoYXQgd291bGQgcmFuZG9tbHkgbWVzcyB1cAoJCQkvLyB0aGUgb3V0cHV0IGluIHlvdXIgYnJvd3Nlci4gSnVzdCBjaGVjayBpdCBhbmQgdGhyb3cgYW4gZXhjZXB0aW9uIGVhcmx5ISkKCQkJLy8KCQkJLy8gWW91IGNvdWxkIGFsc28gaGFyZC1jb2RlIGEgdGVtcGxhdGUgb3IgbG9hZCBvbmUgYXQgY29tcGlsZSB0aW1lIHdpdGggYW4KCQkJLy8gaW1wb3J0IGV4cHJlc3Npb24sIGJ1dCB5b3UgbWlnaHQgYXBwcmVjaWF0ZSBtYWtpbmcgaXQgYSByZWd1bGFyIGZpbGUKCQkJLy8gYmVjYXVzZSB0aGF0IG1lYW5zIGl0IGNhbiBiZSBtb3JlIGVhc2lseSBlZGl0ZWQgYnkgdGhlIGZyb250ZW5kIHRlYW0gYW5kCgkJCS8vIHRoZXkgY2FuIHNlZSB0aGVpciBjaGFuZ2VzIHdpdGhvdXQgbmVlZGluZyB0byByZWNvbXBpbGUgdGhlIHByb2dyYW0uCgkJCS8vCgkJCS8vIE5vdGUgb24gQ1RGRTogaWYgeW91IGRvIGNob29zZSB0byBsb2FkIGEgc3RhdGljIGZpbGUgYXQgY29tcGlsZSB0aW1lLAoJCQkvLyB5b3UgKmNhbiogcGFyc2UgaXQgaW4gQ1RGRSB1c2luZyBlbnVtLCB3aGljaCB3aWxsIGNhdXNlIGl0IHRvIHRocm93IGF0CgkJCS8vIGNvbXBpbGUgdGltZSwgd2hpY2ggaXMga2luZGEgY29vbCB0b28uIEJlIGNhcmVmdWwgaW4gbW9kaWZ5aW5nIHRoYXQgZG9jdW1lbnQsCgkJCS8vIHRob3VnaCwgYXMgaXQgd2lsbCBiZSBhIHN0YXRpYyBpbnN0YW5jZS4gWW91IG1pZ2h0IHdhbnQgdG8gY2xvbmUgb24gb24gZGVtYW5kLAoJCQkvLyBvciBwZXJoYXBzIG1vZGlmeSBpdCBsYXppbHkgYXMgeW91IHByaW50IGl0IG91dC4gKFRyeSBlbGVtZW50LnRyZWUsIGl0IHJldHVybnMKCQkJLy8gYSByYW5nZSBvZiBlbGVtZW50cyB3aGljaCB5b3UgY291bGQgc2VuZCB0aHJvdWdoIHN0ZC5hbGdvcml0aG0gZnVuY3Rpb25zLiBCdXQKCQkJLy8gc2luY2UgbXkgc2VsZWN0b3IgaW1wbGVtZW50YXRpb24gZG9lc24ndCB3b3JrIG9uIHRoYXQgbGV2ZWwgeWV0LCB5b3UnbGwgZmluZCB0aGF0CgkJCS8vIGhhcmRlciB0byB1c2UuIE9mIGNvdXJzZSwgeW91IGNvdWxkIG1ha2UgYSBzdGF0aWMgbGlzdCBvZiBtYXRjaGluZyBlbGVtZW50cyBhbmQKCQkJLy8gdGhlbiB1c2UgYSBzaW1wbGUgZSBpcyBlMiBwcmVkaWNhdGUuLi4gOikgKQoJCQlkb2N1bWVudC5wYXJzZVV0Zjgoc3RkLmZpbGUucmVhZCgieW91cl90ZW1wbGF0ZS5odG1sIiksIHRydWUsIHRydWUpOwoKCQkJLy8gZmlsbCBpbiBkYXRhIHVzaW5nIERPTSBmdW5jdGlvbnMsIHNvIHBsYWNpbmcgaXQgaXMgaW4gdGhlIGhhbmRzIG9mIEhUTUwKCQkJLy8gYW5kIGl0IHdpbGwgYmUgcHJvcGVybHkgZW5jb2RlZCBhcyB0ZXh0IHRvby4KCQkJLy8KCQkJLy8gUGxhaW4gaHRtbCB0ZW1wbGF0ZXMgY2FuJ3QgcnVuIHNlcnZlciBzaWRlIGxvZ2ljLCBidXQgSSB0aGluayB0aGF0J3MgYQoJCQkvLyBnb29kIHRoaW5nIC0gaXQga2VlcHMgdGhlbSBzaW1wbGUuIFlvdSBtYXkgY2hvb3NlIHRvIGV4dGVuZCB0aGUgaHRtbCwKCQkJLy8gYnV0IEkgdGhpbmsgaXQgaXMgYmVzdCB0byB0cnkgdG8gc3RpY2sgdG8gc3RhbmRhcmQgZWxlbWVudHMgYW5kIGZpbGwgdGhlbQoJCQkvLyBpbiB3aXRoIHJlcXVlc3RlZCBkYXRhIHdpdGggSURzIG9yIGNsYXNzIG5hbWVzLiBBIGZ1cnRoZXIgYmVuZWZpdCBvZgoJCQkvLyB0aGlzIGlzIHRoZSBkZXNpZ25lciBjYW4gYWxzbyBoaWdobGlnaHQgZGF0YSBiYXNlZCBvbiBzb3VyY2VzIGluIHRoZSBDU1MuCgkJCS8vCgkJCS8vIEhvd2V2ZXIsIGFsbCBvZiBkb20uZCBpcyBhdmFpbGFibGUsIHNvIHlvdSBjYW4gZm9ybWF0IHlvdXIgZGF0YSBob3dldmVyCgkJCS8vIHlvdSBsaWtlLiBZb3UgY2FuIGRvIHBhcnRpYWwgdGVtcGxhdGVzIHdpdGggaW5uZXJIVE1MIHRvbywgb3IgcGVyaGFwcyBiZXR0ZXIsCgkJCS8vIGluamVjdGluZyBjbG9uZWQgbm9kZXMgZnJvbSBhIHBhcnRpYWwgZG9jdW1lbnQuCgkJCS8vCgkJCS8vIFRoZXJlJ3MgYSBsb3Qgb2YgcG9zc2liaWxpdGllcy4KCQkJZG9jdW1lbnRbIiNuYW1lIl0uaW5uZXJUZXh0ID0gY2dpLnJlcXVlc3QoIm5hbWUiLCAiZGVmYXVsdCBuYW1lIik7CgoJCQkvLyBzZW5kIHRoZSBkb2N1bWVudCB0byB0aGUgYnJvd3Nlci4gVGhlIHNlY29uZCBhcmd1bWVudCB0byBgY2dpLndyaXRlYAoJCQkvLyBpbmRpY2F0ZXMgdGhhdCB0aGlzIGlzIGFsbCB0aGUgZGF0YSBhdCBvbmNlLCBlbmFibGluZyBhIGZldyBzbWFsbAoJCQkvLyBvcHRpbWl6YXRpb25zLgoJCQljZ2kud3JpdGUoZG9jdW1lbnQudG9TdHJpbmcoKSwgdHJ1ZSk7CgkJfQoJLS0tCgoJQ29uY2VwdHM6CgkJSW5wdXQ6IFtDZ2kuZ2V0XSwgW0NnaS5wb3N0XSwgW0NnaS5yZXF1ZXN0XSwgW0NnaS5maWxlc10sIFtDZ2kuY29va2llc10sIFtDZ2kucGF0aEluZm9dLCBbQ2dpLnJlcXVlc3RNZXRob2RdLAoJCSAgICAgICBhbmQgSFRUUCBoZWFkZXJzIChbQ2dpLmhlYWRlcnNdLCBbQ2dpLnVzZXJBZ2VudF0sIFtDZ2kucmVmZXJyZXJdLCBbQ2dpLmFjY2VwdF0sIFtDZ2kuYXV0aG9yaXphdGlvbl0sIFtDZ2kubGFzdEV2ZW50SWRdCgoJCU91dHB1dDogW0NnaS53cml0ZV0sIFtDZ2kuaGVhZGVyXSwgW0NnaS5zZXRSZXNwb25zZVN0YXR1c10sIFtDZ2kuc2V0UmVzcG9uc2VDb250ZW50VHlwZV0sIFtDZ2kuZ3ppcFJlc3BvbnNlXQoKCQlDb29raWVzOiBbQ2dpLnNldENvb2tpZV0sIFtDZ2kuY2xlYXJDb29raWVdLCBbQ2dpLmNvb2tpZV0sIFtDZ2kuY29va2llc10KCgkJQ2FjaGluZzogW0NnaS5zZXRSZXNwb25zZUV4cGlyZXNdLCBbQ2dpLnVwZGF0ZVJlc3BvbnNlRXhwaXJlc10sIFtDZ2kuc2V0Q2FjaGVdCgoJCVJlZGlyZWN0aW9uczogW0NnaS5zZXRSZXNwb25zZUxvY2F0aW9uXQoKCQlPdGhlciBJbmZvcm1hdGlvbjogW0NnaS5yZW1vdGVBZGRyZXNzXSwgW0NnaS5odHRwc10sIFtDZ2kucG9ydF0sIFtDZ2kuc2NyaXB0TmFtZV0sIFtDZ2kucmVxdWVzdFVyaV0sIFtDZ2kuZ2V0Q3VycmVudENvbXBsZXRlVXJpXSwgW0NnaS5vblJlcXVlc3RCb2R5RGF0YVJlY2VpdmVkXQoKCQlPdmVycmlkaW5nIGJlaGF2aW9yOiBbQ2dpLmhhbmRsZUluY29taW5nRGF0YUNodW5rXSwgW0NnaS5wcmVwYXJlRm9ySW5jb21pbmdEYXRhQ2h1bmtzXSwgW0NnaS5jbGVhblVwUG9zdERhdGFTdGF0ZV0KCgkJSW5zdGFsbGluZzogQXBhY2hlLCBJSVMsIENHSSwgRmFzdENHSSwgU0NHSSwgZW1iZWRkZWQgSFRUUEQgKG5vdCByZWNvbW1lbmRlZCBmb3IgcHJvZHVjdGlvbiB1c2UpCgoJR3VpZGVfZm9yX1BIUF91c2VyczoKCQlJZiB5b3UgYXJlIGNvbWluZyBmcm9tIFBIUCwgaGVyZSdzIGEgcXVpY2sgZ3VpZGUgdG8gaGVscCB5b3UgZ2V0IHN0YXJ0ZWQ6CgoJCWBgYAoJCSRfR0VUWyJ2YXIiXSA9PSBjZ2kuZ2V0WyJ2YXIiXQoJCSRfUE9TVFsidmFyIl0gPT0gY2dpLnBvc3RbInZhciJdCgkJJF9DT09LSUVbInZhciJdID09IGNnaS5jb29raWVzWyJ2YXIiXQoJCWBgYAoKCQlJbiBQSFAsIHlvdSBjYW4gZ2l2ZSBhIGZvcm0gZWxlbWVudCBhIG5hbWUgbGlrZSBgInNvbWV0aGluZ1tdImAsIGFuZCB0aGVuCgkJYCRfUE9TVFsic29tZXRoaW5nIl1gIGdpdmVzIGFuIGFycmF5LiBJbiBELCB5b3UgY2FuIHVzZSB3aGF0ZXZlciBuYW1lCgkJeW91IHdhbnQsIGFuZCBhY2Nlc3MgYW4gYXJyYXkgb2YgdmFsdWVzIHdpdGggdGhlIGBjZ2kuZ2V0QXJyYXlbIm5hbWUiXWAgYW5kCgkJYGNnaS5wb3N0QXJyYXlbIm5hbWUiXWAgbWVtYmVycy4KCgkJYGBgCgkJZWNobygiaGVsbG8iKTsgPT0gY2dpLndyaXRlKCJoZWxsbyIpOwoKCQkkX1NFUlZFUlsiUkVNT1RFX0FERFIiXSA9PSBjZ2kucmVtb3RlQWRkcmVzcwoJCSRfU0VSVkVSWyJIVFRQX0hPU1QiXSA9PSBjZ2kuaG9zdAoJCWBgYAoKCVNlZV9BbHNvOgoKCVlvdSBtYXkgYWxzbyB3YW50IHRvIHNlZSBkb20uZCwgd2ViLmQsIGFuZCBodG1sLmQgZm9yIG1vcmUgY29kZSBmb3IgbWFraW5nCgl3ZWIgYXBwbGljYXRpb25zLiBkYXRhYmFzZS5kLCBteXNxbC5kLCBwb3N0Z3Jlcy5kLCBhbmQgc3FsaXRlLmQgY2FuIGhlbHAgaW4KCWFjY2Vzc2luZyBkYXRhYmFzZXMuCgoJSWYgeW91IGFyZSBsb29raW5nIHRvIGFjY2VzcyBhIHdlYiBhcHBsaWNhdGlvbiB2aWEgSFRUUCwgdHJ5IGN1cmwuZC4KCglDb3B5cmlnaHQ6CgoJY2dpLmQgY29weXJpZ2h0IDIwMDgtMjAxNiwgQWRhbSBELiBSdXBwZS4gUHJvdmlkZWQgdW5kZXIgdGhlIEJvb3N0IFNvZnR3YXJlIExpY2Vuc2UuCgoJWWVzLCB0aGlzIGZpbGUgaXMgYWxtb3N0IGVpZ2h0IHllYXJzIG9sZCwgYW5kIHllcywgaXQgaXMgc3RpbGwgYWN0aXZlbHkgbWFpbnRhaW5lZCBhbmQgdXNlZC4KKy8KbW9kdWxlIGFyc2QuY2dpOwoKc3RhdGljIGltcG9ydCBzdGQuZmlsZTsKCnZlcnNpb24oZW1iZWRkZWRfaHR0cGQpIHsKCXZlcnNpb24obGludXgpCgkJdmVyc2lvbj1lbWJlZGRlZF9odHRwZF9wcm9jZXNzZXM7CgllbHNlCgkJdmVyc2lvbj1lbWJlZGRlZF9odHRwZF90aHJlYWRzOwoKCS8qCgl2ZXJzaW9uKHdpdGhfb3BlbnNzbCkgewoJCXByYWdtYShsaWIsICJjcnlwdG8iKTsKCQlwcmFnbWEobGliLCAic3NsIik7Cgl9CgkqLwp9CgplbnVtIGxvbmcgZGVmYXVsdE1heENvbnRlbnRMZW5ndGggPSA1XzAwMF8wMDA7CgovKgoKCVRvIGRvIGEgZmlsZSBkb3dubG9hZCBvZmZlciBpbiB0aGUgYnJvd3NlcjoKCiAgICBjZ2kuc2V0UmVzcG9uc2VDb250ZW50VHlwZSgidGV4dC9jc3YiKTsKICAgIGNnaS5oZWFkZXIoIkNvbnRlbnQtRGlzcG9zaXRpb246IGF0dGFjaG1lbnQ7IGZpbGVuYW1lPVwiY3VzdG9tZXJzLmNzdlwiIik7CiovCgovLyBGSVhNRTogdGhlIGxvY2F0aW9uIGhlYWRlciBpcyBzdXBwb3NlZCB0byBiZSBhbiBhYnNvbHV0ZSB1cmwgSSBndWVzcy4KCi8vIEZJWE1FOiB3b3VsZCBiZSBjb29sIHRvIGZsdXNoIHBhcnQgb2YgYSBkb20gZG9jdW1lbnQgYmVmb3JlIGNvbXBsZXRlCi8vIHNvbWVob3cgaW4gaGVyZSBhbmQgZG9tLmQuCgoKLy8gRklYTUU6IDEwMCBDb250aW51ZSBpbiB0aGUgbnBoIHNlY3Rpb24/IFByb2JhYmx5IGJlbG9uZ3Mgb24gdGhlCi8vIGh0dHBkIGNsYXNzIHRob3VnaC4KCi8vIHRoZXNlIGFyZSBwdWJsaWMgc28geW91IGNhbiBtaXhpbiBHZW5lcmljTWFpbi4KLy8gRklYTUU6IHVzZSBhIGZ1bmN0aW9uIGxldmVsIGltcG9ydCBpbnN0ZWFkIQpwdWJsaWMgaW1wb3J0IHN0ZC5zdHJpbmc7CnB1YmxpYyBpbXBvcnQgc3RkLnN0ZGlvOwpwdWJsaWMgaW1wb3J0IHN0ZC5jb252OwppbXBvcnQgc3RkLnVyaTsKaW1wb3J0IHN0ZC5leGNlcHRpb247CmltcG9ydCBzdGQuYmFzZTY0OwpzdGF0aWMgaW1wb3J0IHN0ZC5hbGdvcml0aG07CmltcG9ydCBzdGQuZGF0ZXRpbWU7CmltcG9ydCBzdGQucmFuZ2U7CgppbXBvcnQgc3RkLnByb2Nlc3M7CgppbXBvcnQgc3RkLnpsaWI7CgoKVFtdIGNvbnN1bWUoVCkoVFtdIHJhbmdlLCBpbnQgY291bnQpIHsKCWlmKGNvdW50ID4gcmFuZ2UubGVuZ3RoKQoJCWNvdW50ID0gcmFuZ2UubGVuZ3RoOwoJcmV0dXJuIHJhbmdlW2NvdW50Li4kXTsKfQoKaW50IGxvY2F0aW9uT2YoVCkoVFtdIGRhdGEsIHN0cmluZyBpdGVtKSB7Cgljb25zdCh1Ynl0ZVtdKSBkID0gY2FzdChjb25zdCh1Ynl0ZVtdKSkgZGF0YTsKCWNvbnN0KHVieXRlW10pIGkgPSBjYXN0KGNvbnN0KHVieXRlW10pKSBpdGVtOwoKCWZvcihpbnQgYSA9IDA7IGEgPCBkLmxlbmd0aDsgYSsrKSB7CgkJaWYoYSArIGkubGVuZ3RoID4gZC5sZW5ndGgpCgkJCXJldHVybiAtMTsKCQlpZihkW2EuLmEraS5sZW5ndGhdID09IGkpCgkJCXJldHVybiBhOwoJfQoKCXJldHVybiAtMTsKfQoKLy8vIElmIHlvdSBhcmUgZG9pbmcgYSBjdXN0b20gY2dpIGNsYXNzLCBtaXhpbmcgdGhpcyBpbiBjYW4gdGFrZSBjYXJlIG9mCi8vLyB0aGUgcmVxdWlyZWQgY29uc3RydWN0b3JzIGZvciB5b3UKbWl4aW4gdGVtcGxhdGUgRm9yd2FyZENnaUNvbnN0cnVjdG9ycygpIHsKCXRoaXMobG9uZyBtYXhDb250ZW50TGVuZ3RoID0gZGVmYXVsdE1heENvbnRlbnRMZW5ndGgsCgkJc3RyaW5nW3N0cmluZ10gZW52ID0gbnVsbCwKCQljb25zdCh1Ynl0ZSlbXSBkZWxlZ2F0ZSgpIHJlYWRkYXRhID0gbnVsbCwKCQl2b2lkIGRlbGVnYXRlKGNvbnN0KHVieXRlKVtdKSBfcmF3RGF0YU91dHB1dCA9IG51bGwsCgkJdm9pZCBkZWxlZ2F0ZSgpIF9mbHVzaCA9IG51bGwKCQkpIHsgc3VwZXIobWF4Q29udGVudExlbmd0aCwgZW52LCByZWFkZGF0YSwgX3Jhd0RhdGFPdXRwdXQsIF9mbHVzaCk7IH0KCgl0aGlzKHN0cmluZ1tdIGFyZ3MpIHsgc3VwZXIoYXJncyk7IH0KCgl0aGlzKAoJCUJ1ZmZlcmVkSW5wdXRSYW5nZSBpbnB1dERhdGEsCgkJc3RyaW5nIGFkZHJlc3MsIHVzaG9ydCBfcG9ydCwKCQlpbnQgcGF0aEluZm9TdGFydHMgPSAwLAoJCWJvb2wgX2h0dHBzID0gZmFsc2UsCgkJdm9pZCBkZWxlZ2F0ZShjb25zdCh1Ynl0ZSlbXSkgX3Jhd0RhdGFPdXRwdXQgPSBudWxsLAoJCXZvaWQgZGVsZWdhdGUoKSBfZmx1c2ggPSBudWxsLAoJCS8vIHRoaXMgcG9pbnRlciB0ZWxscyBpZiB0aGUgY29ubmVjdGlvbiBpcyBzdXBwb3NlZCB0byBiZSBjbG9zZWQgYWZ0ZXIgd2UgaGFuZGxlIHRoaXMKCQlib29sKiBjbG9zZUNvbm5lY3Rpb24gPSBudWxsKQoJewoJCXN1cGVyKGlucHV0RGF0YSwgYWRkcmVzcywgX3BvcnQsIHBhdGhJbmZvU3RhcnRzLCBfaHR0cHMsIF9yYXdEYXRhT3V0cHV0LCBfZmx1c2gsIGNsb3NlQ29ubmVjdGlvbik7Cgl9CgoJdGhpcyhCdWZmZXJlZElucHV0UmFuZ2UgaXIsIGJvb2wqIGNsb3NlQ29ubmVjdGlvbikgeyBzdXBlcihpciwgY2xvc2VDb25uZWN0aW9uKTsgfQp9CgoKIAp2ZXJzaW9uKFdpbmRvd3MpIHsKLy8gRklYTUU6IHVnbHkgaGFjayB0byBzb2x2ZSBzdGRpbiBleGNlcHRpb24gcHJvYmxlbXMgb24gV2luZG93czoKLy8gcmVhZGluZyBzdGRpbiByZXN1bHRzIGluIFN0ZGlvRXhjZXB0aW9uIChCYWQgZmlsZSBkZXNjcmlwdG9yKQovLyB0aGlzIGlzIHByb2JhYmx5IGR1ZSB0byBodHRwOi8vZC5wdXJlbWFnaWMuY29tL2lzc3Vlcy9zaG93X2J1Zy5jZ2k/aWQ9MzQyNQpwcml2YXRlIHN0cnVjdCBzdGRpbiB7CglzdHJ1Y3QgQnlDaHVuayB7IC8vIFJlcGxpY2F0ZXMgc3RkLnN0ZGlvLkJ5Q2h1bmsKCXByaXZhdGU6CgkJdWJ5dGVbXSBjaHVua187CglwdWJsaWM6CgkJdGhpcyhzaXplX3Qgc2l6ZSkKCQlpbiB7CgkJCWFzc2VydChzaXplLCAic2l6ZSBtdXN0IGJlIGxhcmdlciB0aGFuIDAiKTsKCQl9CgkJYm9keSB7CgkJCWNodW5rXyA9IG5ldyB1Ynl0ZVtdKHNpemUpOwoJCQlwb3BGcm9udCgpOwoJCX0KCgkJQHByb3BlcnR5IGJvb2wgZW1wdHkoKSBjb25zdCB7CgkJCXJldHVybiAhc3RkLnN0ZGlvLnN0ZGluLmlzT3BlbiB8fCBzdGQuc3RkaW8uc3RkaW4uZW9mOyAvLyBVZ2x5LCBidXQgc2VlbXMgdG8gZG8gdGhlIGpvYgoJCX0KCQlAcHJvcGVydHkgbm90aHJvdyB1Ynl0ZVtdIGZyb250KCkgewlyZXR1cm4gY2h1bmtfOyB9CgkJdm9pZCBwb3BGcm9udCgpCXsKCQkJZW5mb3JjZSghZW1wdHksICJDYW5ub3QgY2FsbCBwb3BGcm9udCBvbiBlbXB0eSByYW5nZSIpOwoJCQljaHVua18gPSBzdGRpbi5yYXdSZWFkKGNodW5rXyk7CgkJfQoJfQoKCWltcG9ydCBjb3JlLnN5cy53aW5kb3dzLndpbmRvd3M7CnN0YXRpYzoKCglzdGF0aWMgdGhpcygpIHsKCQkvLyBTZXQgc3RkaW4gdG8gYmluYXJ5IG1vZGUKCQl2ZXJzaW9uKFdpbjY0KQoJCV9zZXRtb2RlKHN0ZC5zdGRpby5zdGRpbi5maWxlbm8oKSwgMHg4MDAwKTsKCQllbHNlCgkJc2V0bW9kZShzdGQuc3RkaW8uc3RkaW4uZmlsZW5vKCksIDB4ODAwMCk7Cgl9CgoJVFtdIHJhd1JlYWQoVCkoVFtdIGJ1ZikgewoJCXVpbnQgYnl0ZXNSZWFkOwoJCWF1dG8gcmVzdWx0ID0gUmVhZEZpbGUoR2V0U3RkSGFuZGxlKFNURF9JTlBVVF9IQU5ETEUpLCBidWYucHRyLCBjYXN0KGludCkgKGJ1Zi5sZW5ndGggKiBULnNpemVvZiksICZieXRlc1JlYWQsIG51bGwpOwoKCQlpZiAoIXJlc3VsdCkgewoJCQlhdXRvIGVyciA9IEdldExhc3RFcnJvcigpOwoJCQlpZiAoZXJyID09IDM4LypFUlJPUl9IQU5ETEVfRU9GKi8gfHwgZXJyID09IDEwOS8qRVJST1JfQlJPS0VOX1BJUEUqLykgLy8gJ2dvb2QnIGVycm9ycyBtZWFuaW5nIGVuZCBvZiBpbnB1dAoJCQkJcmV0dXJuIGJ1ZlswLi4wXTsKCQkJLy8gU29tZSBvdGhlciBlcnJvciwgdGhyb3cgaXQKCgkJCWNoYXIqIGJ1ZmZlcjsKCQkJc2NvcGUoZXhpdCkgTG9jYWxGcmVlKGJ1ZmZlcik7CgoJCQkvLyBGT1JNQVRfTUVTU0FHRV9BTExPQ0FURV9CVUZGRVIJPSAweDAwMDAwMTAwCgkJCS8vIEZPUk1BVF9NRVNTQUdFX0ZST01fU1lTVEVNCQk9IDB4MDAwMDEwMDAKCQkJRm9ybWF0TWVzc2FnZUEoMHgxMTAwLCBudWxsLCBlcnIsIDAsIGNhc3QoY2hhciopJmJ1ZmZlciwgMjU2LCBudWxsKTsKCQkJdGhyb3cgbmV3IEV4Y2VwdGlvbih0byFzdHJpbmcoYnVmZmVyKSk7CgkJfQoJCWVuZm9yY2UoIShieXRlc1JlYWQgJSBULnNpemVvZiksICJJL08gZXJyb3IiKTsKCQlyZXR1cm4gYnVmWzAuLmJ5dGVzUmVhZCAvIFQuc2l6ZW9mXTsKCX0KCglhdXRvIGJ5Q2h1bmsoc2l6ZV90IHN6KSB7IHJldHVybiBCeUNodW5rKHN6KTsgfQp9Cn0KCi8vLyBUaGUgbWFpbiBpbnRlcmZhY2Ugd2l0aCB0aGUgd2ViIHJlcXVlc3QKY2xhc3MgQ2dpIHsKICBwdWJsaWM6CgkvLy8gdGhlIG1ldGhvZHMgYSByZXF1ZXN0IGNhbiBiZQoJZW51bSBSZXF1ZXN0TWV0aG9kIHsgR0VULCBIRUFELCBQT1NULCBQVVQsIERFTEVURSwgLy8gR0VUIGFuZCBQT1NUIGFyZSB0aGUgb25lcyB0aGF0IHJlYWxseSB3b3JrCgkJLy8gdGhlc2UgYXJlIGRlZmluZWQgaW4gdGhlIHN0YW5kYXJkLCBidXQgaWRrIGlmIHRoZXkgYXJlIHVzZWZ1bCBmb3IgYW55dGhpbmcKCQlPUFRJT05TLCBUUkFDRSwgQ09OTkVDVCwKCQkvLyBUaGVzZSBzZWVtIG5ldywgSSBoYXZlIG9ubHkgcmVjZW50bHkgc2VlbiB0aGVtCgkJUEFUQ0gsIE1FUkdFLAoJCS8vIHRoaXMgaXMgYW4gZXh0ZW5zaW9uIGZvciB3aGVuIHRoZSBtZXRob2QgaXMgbm90IHNwZWNpZmllZCBhbmQgeW91IHdhbnQgdG8gYXNzdW1lCgkJQ29tbWFuZExpbmUgfQoKCgkvKwoJLysrCgkJQ2dpIHByb3ZpZGVzIGEgcGVyLXJlcXVlc3QgbWVtb3J5IHBvb2wKCgkrLwoJdm9pZFtdIGFsbG9jYXRlTWVtb3J5KHNpemVfdCBuQnl0ZXMpIHsKCgl9CgoJLy8vIGRpdHRvCgl2b2lkW10gcmVhbGxvY2F0ZU1lbW9yeSh2b2lkW10gb2xkLCBzaXplX3QgbkJ5dGVzKSB7CgoJfQoKCS8vLyBkaXR0bwoJdm9pZCBmcmVlTWVtb3J5KHZvaWRbXSBtZW1vcnkpIHsKCgl9CgkrLwoKCi8qCglpbXBvcnQgY29yZS5ydW50aW1lOwoJYXV0byBhcmdzID0gUnVudGltZS5hcmdzKCk7CgoJd2UgY2FuIGNhbGwgdGhlIGFwcCBhIGZldyB3YXlzOgoKCTEpIHNldCB1cCB0aGUgZW52aXJvbm1lbnQgdmFyaWFibGVzIGFuZCBjYWxsIHRoZSBhcHAgKG1hbnVhbGx5IHNpbXVsYXRpbmcgQ0dJKQoJMikgc2ltdWxhdGUgYSBjYWxsIGF1dG9tYXRpY2FsbHk6CgkJLi9hcHAgbWV0aG9kICd1cmknCgoJCWZvciBleGFtcGxlOgoJCQkuL2FwcCBnZXQgL3BhdGg/YXJnIGFyZzI9c29tZXRoaW5nCgoJICBBbnl0aGluZyBvbiB0aGUgdXJpIGlzIHRyZWF0ZWQgYXMgcXVlcnkgc3RyaW5nIGV0YwoKCSAgb24gZ2V0IG1ldGhvZCwgZnVydGhlciBhcmdzIGFyZSBhcHBlbmRlZCB0byB0aGUgcXVlcnkgc3RyaW5nIChlbmNvZGVkIGF1dG9tYXRpY2FsbHkpCgkgIG9uIHBvc3QgbWV0aG9kLCBmdXJ0aGVyIGFyZ3MgYXJlIGRvbmUgYXMgcG9zdAoKCgkgIEBuYW1lIG1lYW5zIGltcG9ydCBmcm9tIGZpbGUgIm5hbWUiLiBpZiBuYW1lID09IC0sIGl0IHVzZXMgc3RkaW4KCSAgKHNvIGluZm89QC0gbWVhbnMgc2V0IGluZm8gdG8gdGhlIHZhbHVlIG9mIHN0ZGluKQoKCgkgIE90aGVyIGFyZ3VtZW50cyBpbmNsdWRlOgoJICAJLS1jb29raWUgbmFtZT12YWx1ZSAodGhlc2UgYXJlIGFsbCBjb25jYXRlZCB0b2dldGhlcikKCQktLWhlYWRlciAnWC1Tb21ldGhpbmc6IGNvb2wnCgkJLS1yZWZlcnJlciAnc29tZXRoaW5nJwoJCS0tcG9ydCA4MAoJCS0tcmVtb3RlLWFkZHJlc3Mgc29tZS5pcC5hZGRyZXNzLmhlcmUKCQktLWh0dHBzIHllcwoJCS0tdXNlci1hZ2VudCAnc29tZXRoaW5nJwoJCS0tdXNlcnBhc3MgJ3VzZXI6cGFzcycKCQktLWF1dGhvcml6YXRpb24gJ0Jhc2ljIGJhc2U2NGVuY29kZWRfdXNlcjpwYXNzJwoJCS0tYWNjZXB0ICdjb250ZW50JyAvLyBGSVhNRTogYmV0dGVyIGV4YW1wbGUKCQktLWxhc3QtZXZlbnQtaWQgJ3NvbWV0aGluZycKCQktLWhvc3QgJ3NvbWV0aGluZy5jb20nCgoJICBOb24tc2ltdWxhdGlvbiBhcmd1bWVudHM6CgkgIAktLXBvcnQgeHh4IGxpc3RlbmluZyBwb3J0IGZvciBub24tY2dpIHRoaW5ncyAodmFsaWQgZm9yIHRoZSBjZ2kgaW50ZXJmYWNlcykKCQktLWxpc3RlbmluZy1ob3N0ICB0aGUgaXAgYWRkcmVzcyB0aGUgYXBwbGljYXRpb24gc2hvdWxkIGxpc3RlbiBvbiAob25seSBpbXBsZW1lbnRlZCBmb3IgZmFzdGNnaSByaWdodCBub3cpCgoqLwoKCS8qKiBJbml0aWFsaXplcyBpdCB3aXRoIGNvbW1hbmQgbGluZSBhcmd1bWVudHMgKGZvciBlYXN5IHRlc3RpbmcpICovCgl0aGlzKHN0cmluZ1tdIGFyZ3MpIHsKCQkvLyB0aGVzZSBhcmUgYWxsIHNldCBsb2NhbGx5IHNvIHRoZSBsb29wIHdvcmtzCgkJLy8gd2l0aG91dCB0cmlnZ2VyaW5nIGVycm9ycyBpbiBkbWQgMi4wNjQKCQkvLyB3ZSBnbyBhaGVhZCBhbmQgc2V0IHRoZW0gYXQgdGhlIGVuZCBvZiBpdCB0byB0aGUgdGhpcyB2ZXJzaW9uCgkJaW50IHBvcnQ7CgkJc3RyaW5nIHJlZmVycmVyOwoJCXN0cmluZyByZW1vdGVBZGRyZXNzOwoJCXN0cmluZyB1c2VyQWdlbnQ7CgkJc3RyaW5nIGF1dGhvcml6YXRpb247CgkJc3RyaW5nIG9yaWdpbjsKCQlzdHJpbmcgYWNjZXB0OwoJCXN0cmluZyBsYXN0RXZlbnRJZDsKCQlib29sIGh0dHBzOwoJCXN0cmluZyBob3N0OwoJCVJlcXVlc3RNZXRob2QgcmVxdWVzdE1ldGhvZDsKCQlzdHJpbmcgcmVxdWVzdFVyaTsKCQlzdHJpbmcgcGF0aEluZm87CgkJc3RyaW5nIHF1ZXJ5U3RyaW5nOwoKCQlib29sIGxvb2tpbmdGb3JNZXRob2Q7CgkJYm9vbCBsb29raW5nRm9yVXJpOwoJCXN0cmluZyBuZXh0QXJnSXM7CgoJCXN0cmluZyBfY29va2llOwoJCXN0cmluZyBfcXVlcnlTdHJpbmc7CgkJc3RyaW5nW11bc3RyaW5nXSBfcG9zdDsKCQlzdHJpbmdbc3RyaW5nXSBfaGVhZGVyczsKCgkJc3RyaW5nW10gYnJlYWtVcChzdHJpbmcgcykgewoJCQlzdHJpbmcgaywgdjsKCQkJYXV0byBpZHggPSBzLmluZGV4T2YoIj0iKTsKCQkJaWYoaWR4ID09IC0xKSB7CgkJCQlrID0gczsKCQkJfSBlbHNlIHsKCQkJCWsgPSBzWzAgLi4gaWR4XTsKCQkJCXYgPSBzW2lkeCArIDEgLi4gJF07CgkJCX0KCgkJCXJldHVybiBbaywgdl07CgkJfQoKCQlsb29raW5nRm9yTWV0aG9kID0gdHJ1ZTsKCgkJc2NyaXB0TmFtZSA9IGFyZ3NbMF07CgkJc2NyaXB0RmlsZU5hbWUgPSBhcmdzWzBdOwoKCQllbnZpcm9ubWVudFZhcmlhYmxlcyA9IGNhc3QoY29uc3QpIGVudmlyb25tZW50LnRvQUE7CgoJCWZvcmVhY2goYXJnOyBhcmdzWzEgLi4gJF0pIHsKCQkJaWYoYXJnLnN0YXJ0c1dpdGgoIi0tIikpIHsKCQkJCW5leHRBcmdJcyA9IGFyZ1syIC4uICRdOwoJCQl9IGVsc2UgaWYobmV4dEFyZ0lzLmxlbmd0aCkgewoJCQkJc3dpdGNoKG5leHRBcmdJcykgewoJCQkJCWNhc2UgImNvb2tpZSI6CgkJCQkJCWF1dG8gaW5mbyA9IGJyZWFrVXAoYXJnKTsKCQkJCQkJaWYoX2Nvb2tpZS5sZW5ndGgpCgkJCQkJCQlfY29va2llIH49ICI7ICI7CgkJCQkJCV9jb29raWUgfj0gc3RkLnVyaS5lbmNvZGVDb21wb25lbnQoaW5mb1swXSkgfiAiPSIgfiBzdGQudXJpLmVuY29kZUNvbXBvbmVudChpbmZvWzFdKTsKCQkJCQlicmVhazsKCQkJCQljYXNlICJwb3J0IjoKCQkJCQkJcG9ydCA9IHRvIWludChhcmcpOwoJCQkJCWJyZWFrOwoJCQkJCWNhc2UgInJlZmVycmVyIjoKCQkJCQkJcmVmZXJyZXIgPSBhcmc7CgkJCQkJYnJlYWs7CgkJCQkJY2FzZSAicmVtb3RlLWFkZHJlc3MiOgoJCQkJCQlyZW1vdGVBZGRyZXNzID0gYXJnOwoJCQkJCWJyZWFrOwoJCQkJCWNhc2UgInVzZXItYWdlbnQiOgoJCQkJCQl1c2VyQWdlbnQgPSBhcmc7CgkJCQkJYnJlYWs7CgkJCQkJY2FzZSAiYXV0aG9yaXphdGlvbiI6CgkJCQkJCWF1dGhvcml6YXRpb24gPSBhcmc7CgkJCQkJYnJlYWs7CgkJCQkJY2FzZSAidXNlcnBhc3MiOgoJCQkJCQlhdXRob3JpemF0aW9uID0gIkJhc2ljICIgfiBCYXNlNjQuZW5jb2RlKGNhc3QoaW1tdXRhYmxlKHVieXRlKVtdKSAoYXJnKSkuaWR1cDsKCQkJCQlicmVhazsKCQkJCQljYXNlICJvcmlnaW4iOgoJCQkJCQlvcmlnaW4gPSBhcmc7CgkJCQkJYnJlYWs7CgkJCQkJY2FzZSAiYWNjZXB0IjoKCQkJCQkJYWNjZXB0ID0gYXJnOwoJCQkJCWJyZWFrOwoJCQkJCWNhc2UgImxhc3QtZXZlbnQtaWQiOgoJCQkJCQlsYXN0RXZlbnRJZCA9IGFyZzsKCQkJCQlicmVhazsKCQkJCQljYXNlICJodHRwcyI6CgkJCQkJCWlmKGFyZyA9PSAieWVzIikKCQkJCQkJCWh0dHBzID0gdHJ1ZTsKCQkJCQlicmVhazsKCQkJCQljYXNlICJoZWFkZXIiOgoJCQkJCQlzdHJpbmcgdGhpbmcsIG90aGVyOwoJCQkJCQlhdXRvIGlkeCA9IGFyZy5pbmRleE9mKCI6Iik7CgkJCQkJCWlmKGlkeCA9PSAtMSkKCQkJCQkJCXRocm93IG5ldyBFeGNlcHRpb24oIm5lZWQgYSBjb2xvbiBpbiBhIGh0dHAgaGVhZGVyIik7CgkJCQkJCXRoaW5nID0gYXJnWzAgLi4gaWR4XTsKCQkJCQkJb3RoZXIgPSBhcmdbaWR4ICsgMS4uICRdOwoJCQkJCQlfaGVhZGVyc1t0aGluZy5zdHJpcC50b0xvd2VyKCldID0gb3RoZXIuc3RyaXA7CgkJCQkJYnJlYWs7CgkJCQkJY2FzZSAiaG9zdCI6CgkJCQkJCWhvc3QgPSBhcmc7CgkJCQkJYnJlYWs7CgkJCQkJZGVmYXVsdDoKCQkJCQkJLy8gc2tpcCwgd2UgZG9uJ3Qga25vdyBpdCBidXQgdGhhdCdzIG9rLCBpdCBtaWdodCBiZSB1c2VkIGVsc2V3aGVyZSBzbyBubyBlcnJvcgoJCQkJfQoKCQkJCW5leHRBcmdJcyA9IG51bGw7CgkJCX0gZWxzZSBpZihsb29raW5nRm9yTWV0aG9kKSB7CgkJCQlsb29raW5nRm9yTWV0aG9kID0gZmFsc2U7CgkJCQlsb29raW5nRm9yVXJpID0gdHJ1ZTsKCgkJCQlpZihhcmcudG9Mb3dlcigpID09ICJjb21tYW5kbGluZSIpCgkJCQkJcmVxdWVzdE1ldGhvZCA9IFJlcXVlc3RNZXRob2QuQ29tbWFuZExpbmU7CgkJCQllbHNlCgkJCQkJcmVxdWVzdE1ldGhvZCA9IHRvIVJlcXVlc3RNZXRob2QoYXJnLnRvVXBwZXIoKSk7CgkJCX0gZWxzZSBpZihsb29raW5nRm9yVXJpKSB7CgkJCQlsb29raW5nRm9yVXJpID0gZmFsc2U7CgoJCQkJcmVxdWVzdFVyaSA9IGFyZzsKCgkJCQlhdXRvIGlkeCA9IGFyZy5pbmRleE9mKCI/Iik7CgkJCQlpZihpZHggPT0gLTEpCgkJCQkJcGF0aEluZm8gPSBhcmc7CgkJCQllbHNlIHsKCQkJCQlwYXRoSW5mbyA9IGFyZ1swIC4uIGlkeF07CgkJCQkJX3F1ZXJ5U3RyaW5nID0gYXJnW2lkeCArIDEgLi4gJF07CgkJCQl9CgkJCX0gZWxzZSB7CgkJCQkvLyBpdCBpcyBhbiBhcmd1bWVudCBvZiBzb21lIHNvcnQKCQkJCWlmKHJlcXVlc3RNZXRob2QgPT0gQ2dpLlJlcXVlc3RNZXRob2QuUE9TVCkgewoJCQkJCWF1dG8gcGFydHMgPSBicmVha1VwKGFyZyk7CgkJCQkJX3Bvc3RbcGFydHNbMF1dIH49IHBhcnRzWzFdOwoJCQkJfSBlbHNlIHsKCQkJCQlpZihfcXVlcnlTdHJpbmcubGVuZ3RoKQoJCQkJCQlfcXVlcnlTdHJpbmcgfj0gIiYiOwoJCQkJCWF1dG8gcGFydHMgPSBicmVha1VwKGFyZyk7CgkJCQkJX3F1ZXJ5U3RyaW5nIH49IHN0ZC51cmkuZW5jb2RlQ29tcG9uZW50KHBhcnRzWzBdKSB+ICI9IiB+IHN0ZC51cmkuZW5jb2RlQ29tcG9uZW50KHBhcnRzWzFdKTsKCQkJCX0KCQkJfQoJCX0KCgkJYWNjZXB0c0d6aXAgPSBmYWxzZTsKCQlrZWVwQWxpdmVSZXF1ZXN0ZWQgPSBmYWxzZTsKCQlyZXF1ZXN0SGVhZGVycyA9IGNhc3QoaW1tdXRhYmxlKSBfaGVhZGVyczsKCgkJY29va2llID0gX2Nvb2tpZTsKCQljb29raWVzQXJyYXkgPSAgZ2V0Q29va2llQXJyYXkoKTsKCQljb29raWVzID0ga2VlcExhc3RPZihjb29raWVzQXJyYXkpOwoKCQlxdWVyeVN0cmluZyA9IF9xdWVyeVN0cmluZzsKCQlnZXRBcnJheSA9IGNhc3QoaW1tdXRhYmxlKSBkZWNvZGVWYXJpYWJsZXMocXVlcnlTdHJpbmcpOwoJCWdldCA9IGtlZXBMYXN0T2YoZ2V0QXJyYXkpOwoKCQlwb3N0QXJyYXkgPSBjYXN0KGltbXV0YWJsZSkgX3Bvc3Q7CgkJcG9zdCA9IGtlZXBMYXN0T2YoX3Bvc3QpOwoKCQkvLyBGSVhNRQoJCWZpbGVzQXJyYXkgPSBudWxsOwoJCWZpbGVzID0gbnVsbDsKCgkJaXNDYWxsZWRXaXRoQ29tbWFuZExpbmVBcmd1bWVudHMgPSB0cnVlOwoKCQl0aGlzLnBvcnQgPSBwb3J0OwoJCXRoaXMucmVmZXJyZXIgPSByZWZlcnJlcjsKCQl0aGlzLnJlbW90ZUFkZHJlc3MgPSByZW1vdGVBZGRyZXNzOwoJCXRoaXMudXNlckFnZW50ID0gdXNlckFnZW50OwoJCXRoaXMuYXV0aG9yaXphdGlvbiA9IGF1dGhvcml6YXRpb247CgkJdGhpcy5vcmlnaW4gPSBvcmlnaW47CgkJdGhpcy5hY2NlcHQgPSBhY2NlcHQ7CgkJdGhpcy5sYXN0RXZlbnRJZCA9IGxhc3RFdmVudElkOwoJCXRoaXMuaHR0cHMgPSBodHRwczsKCQl0aGlzLmhvc3QgPSBob3N0OwoJCXRoaXMucmVxdWVzdE1ldGhvZCA9IHJlcXVlc3RNZXRob2Q7CgkJdGhpcy5yZXF1ZXN0VXJpID0gcmVxdWVzdFVyaTsKCQl0aGlzLnBhdGhJbmZvID0gcGF0aEluZm87CgkJdGhpcy5xdWVyeVN0cmluZyA9IHF1ZXJ5U3RyaW5nOwoJCXRoaXMucG9zdEpzb24gPSBudWxsOwoJfQoKCS8qKiBJbml0aWFsaXplcyBpdCB1c2luZyBhIENHSSBvciBDR0ktbGlrZSBpbnRlcmZhY2UgKi8KCXRoaXMobG9uZyBtYXhDb250ZW50TGVuZ3RoID0gZGVmYXVsdE1heENvbnRlbnRMZW5ndGgsCgkJLy8gdXNlIHRoaXMgdG8gb3ZlcnJpZGUgdGhlIGVudmlyb25tZW50IHZhcmlhYmxlIGxpc3RpbmcKCQlpbiBzdHJpbmdbc3RyaW5nXSBlbnYgPSBudWxsLAoJCS8vIGFuZCB0aGlzIHNob3VsZCByZXR1cm4gYSBjaHVuayBvZiBkYXRhLiByZXR1cm4gZW1wdHkgd2hlbiBkb25lCgkJY29uc3QodWJ5dGUpW10gZGVsZWdhdGUoKSByZWFkZGF0YSA9IG51bGwsCgkJLy8gZmluYWxseSwgdXNlIHRoaXMgdG8gZG8gY3VzdG9tIG91dHB1dCBpZiBuZWVkZWQKCQl2b2lkIGRlbGVnYXRlKGNvbnN0KHVieXRlKVtdKSBfcmF3RGF0YU91dHB1dCA9IG51bGwsCgkJLy8gdG8gZmx1c2ggdGVoIGN1c3RvbSBvdXRwdXQKCQl2b2lkIGRlbGVnYXRlKCkgX2ZsdXNoID0gbnVsbAoJCSkKCXsKCgkJLy8gdGhlc2UgYXJlIGFsbCBzZXQgbG9jYWxseSBzbyB0aGUgbG9vcCB3b3JrcwoJCS8vIHdpdGhvdXQgdHJpZ2dlcmluZyBlcnJvcnMgaW4gZG1kIDIuMDY0CgkJLy8gd2UgZ28gYWhlYWQgYW5kIHNldCB0aGVtIGF0IHRoZSBlbmQgb2YgaXQgdG8gdGhlIHRoaXMgdmVyc2lvbgoJCWludCBwb3J0OwoJCXN0cmluZyByZWZlcnJlcjsKCQlzdHJpbmcgcmVtb3RlQWRkcmVzczsKCQlzdHJpbmcgdXNlckFnZW50OwoJCXN0cmluZyBhdXRob3JpemF0aW9uOwoJCXN0cmluZyBvcmlnaW47CgkJc3RyaW5nIGFjY2VwdDsKCQlzdHJpbmcgbGFzdEV2ZW50SWQ7CgkJYm9vbCBodHRwczsKCQlzdHJpbmcgaG9zdDsKCQlSZXF1ZXN0TWV0aG9kIHJlcXVlc3RNZXRob2Q7CgkJc3RyaW5nIHJlcXVlc3RVcmk7CgkJc3RyaW5nIHBhdGhJbmZvOwoJCXN0cmluZyBxdWVyeVN0cmluZzsKCgoKCQlpc0NhbGxlZFdpdGhDb21tYW5kTGluZUFyZ3VtZW50cyA9IGZhbHNlOwoJCXJhd0RhdGFPdXRwdXQgPSBfcmF3RGF0YU91dHB1dDsKCQlmbHVzaERlbGVnYXRlID0gX2ZsdXNoOwoJCWF1dG8gZ2V0ZW52ID0gZGVsZWdhdGUgc3RyaW5nKHN0cmluZyB2YXIpIHsKCQkJaWYoZW52IGlzIG51bGwpCgkJCQlyZXR1cm4gc3RkLnByb2Nlc3MuZW52aXJvbm1lbnQuZ2V0KHZhcik7CgkJCWF1dG8gZSA9IHZhciBpbiBlbnY7CgkJCWlmKGUgaXMgbnVsbCkKCQkJCXJldHVybiBudWxsOwoJCQlyZXR1cm4gKmU7CgkJfTsKCgkJZW52aXJvbm1lbnRWYXJpYWJsZXMgPSBlbnYgaXMgbnVsbCA/CgkJCWNhc3QoY29uc3QpIGVudmlyb25tZW50LnRvQUEgOgoJCQllbnY7CgoJCS8vIGZldGNoaW5nIGFsbCB0aGUgcmVxdWVzdCBoZWFkZXJzCgkJc3RyaW5nW3N0cmluZ10gcmVxdWVzdEhlYWRlcnNIZXJlOwoJCWZvcmVhY2goaywgdjsgZW52IGlzIG51bGwgPyBjYXN0KGNvbnN0KSBlbnZpcm9ubWVudC50b0FBKCkgOiBlbnYpIHsKCQkJaWYoay5zdGFydHNXaXRoKCJIVFRQXyIpKSB7CgkJCQlyZXF1ZXN0SGVhZGVyc0hlcmVbcmVwbGFjZShrWyJIVFRQXyIubGVuZ3RoIC4uICRdLnRvTG93ZXIoKSwgIl8iLCAiLSIpXSA9IHY7CgkJCX0KCQl9CgoJCXRoaXMucmVxdWVzdEhlYWRlcnMgPSBhc3N1bWVVbmlxdWUocmVxdWVzdEhlYWRlcnNIZXJlKTsKCgkJcmVxdWVzdFVyaSA9IGdldGVudigiUkVRVUVTVF9VUkkiKTsKCgkJY29va2llID0gZ2V0ZW52KCJIVFRQX0NPT0tJRSIpOwoJCWNvb2tpZXNBcnJheSA9IGdldENvb2tpZUFycmF5KCk7CgkJY29va2llcyA9IGtlZXBMYXN0T2YoY29va2llc0FycmF5KTsKCgkJcmVmZXJyZXIgPSBnZXRlbnYoIkhUVFBfUkVGRVJFUiIpOwoJCXVzZXJBZ2VudCA9IGdldGVudigiSFRUUF9VU0VSX0FHRU5UIik7CgkJcmVtb3RlQWRkcmVzcyA9IGdldGVudigiUkVNT1RFX0FERFIiKTsKCQlob3N0ID0gZ2V0ZW52KCJIVFRQX0hPU1QiKTsKCQlwYXRoSW5mbyA9IGdldGVudigiUEFUSF9JTkZPIik7CgoJCXF1ZXJ5U3RyaW5nID0gZ2V0ZW52KCJRVUVSWV9TVFJJTkciKTsKCQlzY3JpcHROYW1lID0gZ2V0ZW52KCJTQ1JJUFRfTkFNRSIpOwoJCXsKCQkJaW1wb3J0IGNvcmUucnVudGltZTsKCQkJYXV0byBzZm4gPSBnZXRlbnYoIlNDUklQVF9GSUxFTkFNRSIpOwoJCQlzY3JpcHRGaWxlTmFtZSA9IHNmbi5sZW5ndGggPyBzZm4gOiBSdW50aW1lLmFyZ3NbMF07CgkJfQoKCQlib29sIGlpcyA9IGZhbHNlOwoKCQkvLyBCZWNhdXNlIElJUyBkb2Vzbid0IHBhc3MgcmVxdWVzdFVyaSwgd2Ugc2ltdWxhdGUgaXQgaGVyZSBpZiBpdCdzIGVtcHR5LgoJCWlmKHJlcXVlc3RVcmkubGVuZ3RoID09IDApIHsKCQkJLy8gSUlTIHNvbWV0aW1lcyBpbmNsdWRlcyB0aGUgc2NyaXB0IG5hbWUgYXMgcGFydCBvZiB0aGUgcGF0aCBpbmZvIC0gd2UgZG9uJ3Qgd2FudCB0aGF0CgkJCWlmKHBhdGhJbmZvLmxlbmd0aCA+PSBzY3JpcHROYW1lLmxlbmd0aCAmJiAocGF0aEluZm9bMCAuLiBzY3JpcHROYW1lLmxlbmd0aF0gPT0gc2NyaXB0TmFtZSkpCgkJCQlwYXRoSW5mbyA9IHBhdGhJbmZvW3NjcmlwdE5hbWUubGVuZ3RoIC4uICRdOwoKCQkJcmVxdWVzdFVyaSA9IHNjcmlwdE5hbWUgfiBwYXRoSW5mbyB+IChxdWVyeVN0cmluZy5sZW5ndGggPyAoIj8iIH4gcXVlcnlTdHJpbmcpIDogIiIpOwoKCQkJaWlzID0gdHJ1ZTsgLy8gRklYTUUgSEFDSyAtIHVzZWQgaW4gYnlDaHVuayBiZWxvdyAtIHNlZSBidWd6aWxsYSA2MzM5CgoJCQkvLyBGSVhNRTogdGhpcyB3b3JrcyBmb3IgYXBhY2hlIGFuZCBpaXMuLi4gYnV0IHdoYXQgYWJvdXQgb3RoZXJzPwoJCX0KCgoJCWdldCA9IGdldEdldFZhcmlhYmxlcyhxdWVyeVN0cmluZyk7CgkJYXV0byB1Z2ggPSBkZWNvZGVWYXJpYWJsZXMocXVlcnlTdHJpbmcpOwoJCWdldEFycmF5ID0gYXNzdW1lVW5pcXVlKHVnaCk7CgoKCQkvLyBOT1RFOiBvbiBzaGl0cGFjaGUsIHlvdSBuZWVkIHRvIHNwZWNpZmljYWxseSBmb3J3YXJkIHRoaXMKCQlhdXRob3JpemF0aW9uID0gZ2V0ZW52KCJIVFRQX0FVVEhPUklaQVRJT04iKTsKCQkvLyB0aGlzIGlzIGEgaGFjayBiZWNhdXNlIEFwYWNoZSBpcyBhIHNoaXRsb2FkIG9mIGZ1Y2sgYW5kCgkJLy8gcmVmdXNlcyB0byBzZW5kIHRoZSByZWFsIGhlYWRlciB0byB1cy4gQ29tcGF0aWJsZQoJCS8vIHByb2dyYW1zIHNob3VsZCBzZW5kIGJvdGggdGhlIHN0YW5kYXJkIGFuZCBYLSB2ZXJzaW9ucwoKCQkvLyBOT1RFOiBpZiB5b3UgaGF2ZSBhY2Nlc3MgdG8gLmh0YWNjZXNzIG9yIGh0dHBkLmNvbmYsIHlvdSBjYW4gbWFrZSB0aGlzCgkJLy8gdW5uZWNlc3Nhcnkgd2l0aCBtb2RfcmV3cml0ZSwgc28gaXQgaXMgY29tbWVudGVkCgoJCS8vaWYoYXV0aG9yaXphdGlvbi5sZW5ndGggPT0gMCkgLy8gaWYgdGhlIHN0ZCBpcyB0aGVyZSwgdXNlIGl0CgkJLy8JYXV0aG9yaXphdGlvbiA9IGdldGVudigiSFRUUF9YX0FVVEhPUklaQVRJT04iKTsKCgkJLy8gdGhlIFJFRElSRUNUX0hUVFBTIGNoZWNrIGlzIGhlcmUgYmVjYXVzZSB3aXRoIGFuIEFwYWNoZSBoYWNrLCB0aGUgcG9ydCBjYW4gYmVjb21lIHdyb25nCgkJaWYoZ2V0ZW52KCJTRVJWRVJfUE9SVCIpLmxlbmd0aCAmJiBnZXRlbnYoIlJFRElSRUNUX0hUVFBTIikgIT0gIm9uIikKCQkJcG9ydCA9IHRvIWludChnZXRlbnYoIlNFUlZFUl9QT1JUIikpOwoJCWVsc2UKCQkJcG9ydCA9IDA7IC8vIHRoaXMgd2FzIHByb2JhYmx5IGNhbGxlZCBmcm9tIHRoZSBjb21tYW5kIGxpbmUKCgkJYXV0byBhZSA9IGdldGVudigiSFRUUF9BQ0NFUFRfRU5DT0RJTkciKTsKCQlpZihhZS5sZW5ndGggJiYgYWUuaW5kZXhPZigiZ3ppcCIpICE9IC0xKQoJCQlhY2NlcHRzR3ppcCA9IHRydWU7CgoJCWFjY2VwdCA9IGdldGVudigiSFRUUF9BQ0NFUFQiKTsKCQlsYXN0RXZlbnRJZCA9IGdldGVudigiSFRUUF9MQVNUX0VWRU5UX0lEIik7CgoJCWF1dG8ga2EgPSBnZXRlbnYoIkhUVFBfQ09OTkVDVElPTiIpOwoJCWlmKGthLmxlbmd0aCAmJiBrYS50b0xvd2VyKCkuaW5kZXhPZigia2VlcC1hbGl2ZSIpICE9IC0xKQoJCQlrZWVwQWxpdmVSZXF1ZXN0ZWQgPSB0cnVlOwoKCQlhdXRvIG9yID0gZ2V0ZW52KCJIVFRQX09SSUdJTiIpOwoJCQlvcmlnaW4gPSBvcjsKCgkJYXV0byBybSA9IGdldGVudigiUkVRVUVTVF9NRVRIT0QiKTsKCQlpZihybS5sZW5ndGgpCgkJCXJlcXVlc3RNZXRob2QgPSB0byFSZXF1ZXN0TWV0aG9kKGdldGVudigiUkVRVUVTVF9NRVRIT0QiKSk7CgkJZWxzZQoJCQlyZXF1ZXN0TWV0aG9kID0gUmVxdWVzdE1ldGhvZC5Db21tYW5kTGluZTsKCgkJCQkJCS8vIEZJWE1FOiBoYWNrIG9uIFJFRElSRUNUX0hUVFBTOyB0aGlzIGlzIHRoZXJlIGJlY2F1c2UgdGhlIHdvcmsgYXBwIHVzZXMgbW9kX3Jld3JpdGUgd2hpY2ggbG9zZXMgdGhlIGh0dHBzIGZsYWchIFNvIEkgc2V0IGl0IHdpdGggW0U9SFRUUFM9JUhUVFBTXSBvciB3aGF0ZXZlciBidXQgdGhlbiBpdCBnZXRzIHRyYW5zbGF0ZWQgdG8gaGVyZSBzbyBpIHdhbnQgaXQgdG8gc3RpbGwgd29yay4gVGhpcyBpcyBhcmd1YWJseSB3cm9uZyBidXQgbWVoLgoJCWh0dHBzID0gKGdldGVudigiSFRUUFMiKSA9PSAib24iIHx8IGdldGVudigiUkVESVJFQ1RfSFRUUFMiKSA9PSAib24iKTsKCgkJLy8gRklYTUU6IERPQ1VNRU5UX1JPT1Q/CgoJCS8vIEZJWE1FOiB3aGF0IGFib3V0IFBVVD8KCQlpZihyZXF1ZXN0TWV0aG9kID09IFJlcXVlc3RNZXRob2QuUE9TVCkgewoJCQl2ZXJzaW9uKHByZXNlcnZlRGF0YSkgLy8gYSBoYWNrIHRvIG1ha2UgZm9yd2FyZGluZyBzaW1wbGVyCgkJCQlpbW11dGFibGUodWJ5dGUpW10gZGF0YTsKCQkJc2l6ZV90IGFtb3VudFJlY2VpdmVkID0gMDsKCQkJYXV0byBjb250ZW50VHlwZSA9IGdldGVudigiQ09OVEVOVF9UWVBFIik7CgoJCQkvLyBGSVhNRTogaXMgdGhpcyBldmVyIG5vdCBnb2luZyB0byBiZSBzZXQ/IEkgZ3Vlc3MgaXQgZGVwZW5kcwoJCQkvLyBvbiBpZiB0aGUgc2VydmVyIGRlLWNodW5rcyBhbmQgYnVmZmVycy4uLiBzZWVtcyBsaWtlIGl0IGhhcyBwb3RlbnRpYWwKCQkJLy8gdG8gYmUgc2xvdyBpZiB0aGV5IGRpZCB0aGF0LiBUaGUgc3BlYyBzYXlzIGl0IGlzIGFsd2F5cyB0aGVyZSB0aG91Z2guCgkJCS8vIEFuZCBpdCBoYXMgd29ya2VkIHJlbGlhYmx5IGZvciBtZSBhbGwgeWVhciBpbiB0aGUgbGl2ZSBlbnZpcm9ubWVudCwKCQkJLy8gYnV0IHNvbWUgc2VydmVycyBtaWdodCBiZSBkaWZmZXJlbnQuCgkJCWF1dG8gY29udGVudExlbmd0aCA9IHRvIXNpemVfdChnZXRlbnYoIkNPTlRFTlRfTEVOR1RIIikpOwoKCQkJaW1tdXRhYmxlIG9yaWdpbmFsQ29udGVudExlbmd0aCA9IGNvbnRlbnRMZW5ndGg7CgkJCWlmKGNvbnRlbnRMZW5ndGgpIHsKCQkJCWlmKG1heENvbnRlbnRMZW5ndGggPiAwICYmIGNvbnRlbnRMZW5ndGggPiBtYXhDb250ZW50TGVuZ3RoKSB7CgkJCQkJc2V0UmVzcG9uc2VTdGF0dXMoIjQxMyBSZXF1ZXN0IGVudGl0eSB0b28gbGFyZ2UiKTsKCQkJCQl3cml0ZSgiWW91IHRyaWVkIHRvIHVwbG9hZCBhIGZpbGUgdGhhdCBpcyB0b28gbGFyZ2UuIik7CgkJCQkJY2xvc2UoKTsKCQkJCQl0aHJvdyBuZXcgRXhjZXB0aW9uKCJQT1NUIHRvbyBsYXJnZSIpOwoJCQkJfQoJCQkJcHJlcGFyZUZvckluY29taW5nRGF0YUNodW5rcyhjb250ZW50VHlwZSwgY29udGVudExlbmd0aCk7CgoKCQkJCWludCBwcm9jZXNzQ2h1bmsoaW4gdWJ5dGVbXSBjaHVuaykgewoJCQkJCWlmKGNodW5rLmxlbmd0aCA+IGNvbnRlbnRMZW5ndGgpIHsKCQkJCQkJaGFuZGxlSW5jb21pbmdEYXRhQ2h1bmsoY2h1bmtbMC4uY29udGVudExlbmd0aF0pOwoJCQkJCQlhbW91bnRSZWNlaXZlZCArPSBjb250ZW50TGVuZ3RoOwoJCQkJCQljb250ZW50TGVuZ3RoID0gMDsKCQkJCQkJcmV0dXJuIDE7CgkJCQkJfSBlbHNlIHsKCQkJCQkJaGFuZGxlSW5jb21pbmdEYXRhQ2h1bmsoY2h1bmspOwoJCQkJCQljb250ZW50TGVuZ3RoIC09IGNodW5rLmxlbmd0aDsKCQkJCQkJYW1vdW50UmVjZWl2ZWQgKz0gY2h1bmsubGVuZ3RoOwoJCQkJCX0KCQkJCQlpZihjb250ZW50TGVuZ3RoID09IDApCgkJCQkJCXJldHVybiAxOwoKCQkJCQlvblJlcXVlc3RCb2R5RGF0YVJlY2VpdmVkKGFtb3VudFJlY2VpdmVkLCBvcmlnaW5hbENvbnRlbnRMZW5ndGgpOwoJCQkJCXJldHVybiAwOwoJCQkJfQoKCgkJCQlpZihyZWFkZGF0YSBpcyBudWxsKSB7CgkJCQkJZm9yZWFjaCh1Ynl0ZVtdIGNodW5rOyBzdGRpbi5ieUNodW5rKGlpcyA/IGNvbnRlbnRMZW5ndGggOiA0MDk2KSkKCQkJCQkJaWYocHJvY2Vzc0NodW5rKGNodW5rKSkKCQkJCQkJCWJyZWFrOwoJCQkJfSBlbHNlIHsKCQkJCQkvLyB3ZSBoYXZlIGEgY3VzdG9tIGRhdGEgc291cmNlLi4KCQkJCQlhdXRvIGNodW5rID0gcmVhZGRhdGEoKTsKCQkJCQl3aGlsZShjaHVuay5sZW5ndGgpIHsKCQkJCQkJaWYocHJvY2Vzc0NodW5rKGNodW5rKSkKCQkJCQkJCWJyZWFrOwoJCQkJCQljaHVuayA9IHJlYWRkYXRhKCk7CgkJCQkJfQoJCQkJfQoKCQkJCW9uUmVxdWVzdEJvZHlEYXRhUmVjZWl2ZWQoYW1vdW50UmVjZWl2ZWQsIG9yaWdpbmFsQ29udGVudExlbmd0aCk7CgkJCQlwb3N0QXJyYXkgPSBhc3N1bWVVbmlxdWUocHBzLl9wb3N0KTsKCQkJCWZpbGVzQXJyYXkgPSBhc3N1bWVVbmlxdWUocHBzLl9maWxlcyk7CgkJCQlmaWxlcyA9IGtlZXBMYXN0T2YoZmlsZXNBcnJheSk7CgkJCQlwb3N0ID0ga2VlcExhc3RPZihwb3N0QXJyYXkpOwoJCQkJdGhpcy5wb3N0SnNvbiA9IHBwcy5wb3N0SnNvbjsKCQkJCWNsZWFuVXBQb3N0RGF0YVN0YXRlKCk7CgkJCX0KCgkJCXZlcnNpb24ocHJlc2VydmVEYXRhKQoJCQkJb3JpZ2luYWxQb3N0RGF0YSA9IGRhdGE7CgkJfQoJCS8vIGZpeG1lOiByZW1vdGVfdXNlciBzY3JpcHQgbmFtZQoKCgkJdGhpcy5wb3J0ID0gcG9ydDsKCQl0aGlzLnJlZmVycmVyID0gcmVmZXJyZXI7CgkJdGhpcy5yZW1vdGVBZGRyZXNzID0gcmVtb3RlQWRkcmVzczsKCQl0aGlzLnVzZXJBZ2VudCA9IHVzZXJBZ2VudDsKCQl0aGlzLmF1dGhvcml6YXRpb24gPSBhdXRob3JpemF0aW9uOwoJCXRoaXMub3JpZ2luID0gb3JpZ2luOwoJCXRoaXMuYWNjZXB0ID0gYWNjZXB0OwoJCXRoaXMubGFzdEV2ZW50SWQgPSBsYXN0RXZlbnRJZDsKCQl0aGlzLmh0dHBzID0gaHR0cHM7CgkJdGhpcy5ob3N0ID0gaG9zdDsKCQl0aGlzLnJlcXVlc3RNZXRob2QgPSByZXF1ZXN0TWV0aG9kOwoJCXRoaXMucmVxdWVzdFVyaSA9IHJlcXVlc3RVcmk7CgkJdGhpcy5wYXRoSW5mbyA9IHBhdGhJbmZvOwoJCXRoaXMucXVlcnlTdHJpbmcgPSBxdWVyeVN0cmluZzsKCX0KCgkvLy8gQ2xlYW5zIHVwIGFueSB0ZW1wb3JhcnkgZmlsZXMuIERvIG5vdCB1c2UgdGhlIG9iamVjdAoJLy8vIGFmdGVyIGNhbGxpbmcgdGhpcy4KCS8vLwoJLy8vIE5PVEU6IGl0IGlzIGNhbGxlZCBhdXRvbWF0aWNhbGx5IGJ5IEdlbmVyaWNNYWluCgkvLyBGSVhNRTogdGhpcyBzaG91bGQgYmUgY2FsbGVkIGlmIHRoZSBjb25zdHJ1Y3RvciBmYWlscyB0b28sIGlmIGl0IGhhcyBjcmVhdGVkIHNvbWUgZ2FyYmFnZS4uLgoJdm9pZCBkaXNwb3NlKCkgewoJCWZvcmVhY2goZmlsZTsgZmlsZXMpIHsKCQkJaWYoIWZpbGUuY29udGVudEluTWVtb3J5KQoJCQkJaWYoc3RkLmZpbGUuZXhpc3RzKGZpbGUuY29udGVudEZpbGVuYW1lKSkKCQkJCQlzdGQuZmlsZS5yZW1vdmUoZmlsZS5jb250ZW50RmlsZW5hbWUpOwoJCX0KCX0KCglwcml2YXRlIHsKCQlzdHJ1Y3QgUG9zdFBhcnNlclN0YXRlIHsKCQkJc3RyaW5nIGNvbnRlbnRUeXBlOwoJCQlzdHJpbmcgYm91bmRhcnk7CgkJCXN0cmluZyBsb2NhbEJvdW5kYXJ5OyAvLyB0aGUgb25lcyB1c2VkIGF0IHRoZSBlbmQgb3Igc29tZXRoaW5nIGxvbAoJCQlib29sIGlzTXVsdGlwYXJ0OwoJCQlib29sIGlzSnNvbjsKCgkJCXVsb25nIGV4cGVjdGVkTGVuZ3RoOwoJCQl1bG9uZyBjb250ZW50Q29uc3VtZWQ7CgkJCWltbXV0YWJsZSh1Ynl0ZSlbXSBidWZmZXI7CgoJCQkvLyBtdWx0aXBhcnQgcGFyc2luZyBzdGF0ZQoJCQlpbnQgd2hhdERvV2VXYW50OwoJCQlib29sIHdlSGF2ZUFQYXJ0OwoJCQlzdHJpbmdbXSB0aGlzT25lc0hlYWRlcnM7CgkJCWltbXV0YWJsZSh1Ynl0ZSlbXSB0aGlzT25lc0RhdGE7CgoJCQlzdHJpbmcgcG9zdEpzb247CgoJCQlVcGxvYWRlZEZpbGUgcGllY2U7CgkJCWJvb2wgaXNGaWxlID0gZmFsc2U7CgoJCQlzaXplX3QgbWVtb3J5Q29tbWl0dGVkOwoKCQkJLy8gZG8gTk9UIGtlZXAgbXV0YWJsZSByZWZlcmVuY2VzIHRvIHRoZXNlIGFueXdoZXJlIQoJCQkvLyBJIGFzc3VtZSB0aGV5IGFyZSB1bmlxdWUgaW4gdGhlIGNvbnN0cnVjdG9yIG9uY2Ugd2UncmUgYWxsIGRvbmUgZ2V0dGluZyBkYXRhLgoJCQlzdHJpbmdbXVtzdHJpbmddIF9wb3N0OwoJCQlVcGxvYWRlZEZpbGVbXVtzdHJpbmddIF9maWxlczsKCQl9CgoJCVBvc3RQYXJzZXJTdGF0ZSBwcHM7Cgl9CgoJLy8vIFRoaXMgcmVwcmVzZW50cyBhIGZpbGUgdGhlIHVzZXIgdXBsb2FkZWQgdmlhIGEgUE9TVCByZXF1ZXN0LgoJc3RhdGljIHN0cnVjdCBVcGxvYWRlZEZpbGUgewoJCS8vLyBJZiB5b3Ugd2FudCB0byBjcmVhdGUgb25lIG9mIHRoZXNlIHN0cnVjdHMgZm9yIHlvdXJzZWxmIGZyb20gc29tZSBkYXRhLAoJCS8vLyB1c2UgdGhpcyBmdW5jdGlvbi4KCQlzdGF0aWMgVXBsb2FkZWRGaWxlIGZyb21EYXRhKGltbXV0YWJsZSh2b2lkKVtdIGRhdGEsIHN0cmluZyBuYW1lID0gbnVsbCkgewoJCQlDZ2kuVXBsb2FkZWRGaWxlIGY7CgkJCWYuZmlsZW5hbWUgPSBuYW1lOwoJCQlmLmNvbnRlbnQgPSBjYXN0KGltbXV0YWJsZSh1Ynl0ZSlbXSkgZGF0YTsKCQkJZi5jb250ZW50SW5NZW1vcnkgPSB0cnVlOwoJCQlyZXR1cm4gZjsKCQl9CgoJCXN0cmluZyBuYW1lOyAJCS8vLyBUaGUgbmFtZSBvZiB0aGUgZm9ybSBlbGVtZW50LgoJCXN0cmluZyBmaWxlbmFtZTsgCS8vLyBUaGUgZmlsZW5hbWUgdGhlIHVzZXIgc2V0LgoJCXN0cmluZyBjb250ZW50VHlwZTsgCS8vLyBUaGUgTUlNRSB0eXBlIHRoZSB1c2VyJ3MgYnJvd3NlciByZXBvcnRlZC4gKE5vdCByZWxpYWJsZS4pCgoJCS8qKgoJCQlGb3Igc21hbGwgZmlsZXMsIGNnaS5kIHdpbGwgYnVmZmVyIHRoZSB1cGxvYWRlZCBmaWxlIGluIG1lbW9yeSwgYW5kIG1ha2UgaXQKCQkJZGlyZWN0bHkgYWNjZXNzaWJsZSB0byB5b3UgdGhyb3VnaCB0aGUgY29udGVudCBtZW1iZXIuIEkgZmluZCB0aGlzIHZlcnkgY29udmVuaWVudAoJCQlhbmQgc29tZXdoYXQgZWZmaWNpZW50LCBzaW5jZSBpdCBjYW4gYXZvaWQgaGl0dGluZyB0aGUgZGlzayBlbnRpcmVseS4gKEkKCQkJb2Z0ZW4gd2FudCB0byBpbnNwZWN0IGFuZCBtb2RpZnkgdGhlIGZpbGUgYW55d2F5ISkKCgkJCUkgZmluZCB0aGUgZmlsZSBpcyB2ZXJ5IGxhcmdlLCBpdCBpcyB1bmRlc2lyYWJsZSB0byBlYXQgdGhhdCBtdWNoIG1lbW9yeSBqdXN0CgkJCWZvciBhIGZpbGUgYnVmZmVyLiBJbiB0aG9zZSBjYXNlcywgaWYgeW91IHBhc3MgYSBsYXJnZSBlbm91Z2ggdmFsdWUgZm9yIG1heENvbnRlbnRMZW5ndGgKCQkJdG8gdGhlIGNvbnN0cnVjdG9yIHNvIHRoZXkgYXJlIGFjY2VwdGVkLCBjZ2kuZCB3aWxsIHdyaXRlIHRoZSBjb250ZW50IHRvIGEgdGVtcG9yYXJ5CgkJCWZpbGUgdGhhdCB5b3UgY2FuIHJlLXJlYWQgbGF0ZXIuCgoJCQlZb3UgY2FuIG92ZXJyaWRlIHRoaXMgYmVoYXZpb3IgYnkgc3ViY2xhc3NpbmcgQ2dpIGFuZCBvdmVycmlkaW5nIHRoZSBwcm90ZWN0ZWQKCQkJaGFuZGxlUG9zdENodW5rIG1ldGhvZC4gTm90ZSB0aGF0IHRoZSBvYmplY3QgaXMgbm90IGluaXRpYWxpemVkIHdoZW4geW91CgkJCXdyaXRlIHRoYXQgbWV0aG9kIC0gdGhlIGh0dHAgaGVhZGVycyBhcmUgYXZhaWxhYmxlLCBidXQgdGhlIGNnaS5wb3N0IG1ldGhvZAoJCQlpcyBub3QuIFlvdSBtYXkgcGFyc2UgdGhlIGZpbGUgYXMgaXQgc3RyZWFtcyBpbiB1c2luZyB0aGlzIG1ldGhvZC4KCgoJCQlBbnl3YXksIGlmIHRoZSBmaWxlIGlzIHNtYWxsIGVub3VnaCB0byBiZSBpbiBtZW1vcnksIGNvbnRlbnRJbk1lbW9yeSB3aWxsIGJlCgkJCXNldCB0byB0cnVlLCBhbmQgdGhlIGNvbnRlbnQgaXMgYXZhaWxhYmxlIGluIHRoZSBjb250ZW50IG1lbWJlci4KCgkJCUlmIG5vdCwgY29udGVudEluTWVtb3J5IHdpbGwgYmUgc2V0IHRvIGZhbHNlLCBhbmQgdGhlIGNvbnRlbnQgc2F2ZWQgaW4gYSBmaWxlLAoJCQl3aG9zZSBuYW1lIHdpbGwgYmUgYXZhaWxhYmxlIGluIHRoZSBjb250ZW50RmlsZW5hbWUgbWVtYmVyLgoKCgkJCVRpcDogaWYgeW91IGtub3cgeW91IGFyZSBhbHdheXMgZGVhbGluZyB3aXRoIHNtYWxsIGZpbGVzLCBhbmQgd2FudCB0aGUgY29udmVuaWVuY2UKCQkJb2YgaWdub3JpbmcgdGhpcyBtZW1iZXIsIGNvbnN0cnVjdCBDZ2kgd2l0aCBhIHNtYWxsIG1heENvbnRlbnRMZW5ndGguIFRoZW4sIGlmCgkJCWEgbGFyZ2UgZmlsZSBjb21lcyBpbiwgaXQgc2ltcGx5IHRocm93cyBhbiBleGNlcHRpb24gKGFuZCBIVFRQIGVycm9yIHJlc3BvbnNlKQoJCQlpbnN0ZWFkIG9mIHRyeWluZyB0byBoYW5kbGUgaXQuCgoJCQlUaGUgZGVmYXVsdCB2YWx1ZSBvZiBtYXhDb250ZW50TGVuZ3RoIGluIHRoZSBjb25zdHJ1Y3RvciBpcyBmb3Igc21hbGwgZmlsZXMuCgkJKi8KCQlib29sIGNvbnRlbnRJbk1lbW9yeSA9IHRydWU7IC8vIHRoZSBkZWZhdWx0IG91Z2h0IHRvIGFsd2F5cyBiZSB0cnVlCgkJaW1tdXRhYmxlKHVieXRlKVtdIGNvbnRlbnQ7IC8vLyBUaGUgYWN0dWFsIGNvbnRlbnQgb2YgdGhlIGZpbGUsIGlmIGNvbnRlbnRJbk1lbW9yeSA9PSB0cnVlCgkJc3RyaW5nIGNvbnRlbnRGaWxlbmFtZTsgLy8vIHRoZSBmaWxlIHdoZXJlIHdlIGR1bXBlZCB0aGUgY29udGVudCwgaWYgY29udGVudEluTWVtb3J5ID09IGZhbHNlLiBOb3RlIHRoYXQgaWYgeW91IHdhbnQgdG8ga2VlcCBpdCwgeW91IE1VU1QgbW92ZSB0aGUgZmlsZSwgc2luY2Ugb3RoZXJ3aXNlIGl0IGlzIGNvbnNpZGVyZWQgZ2FyYmFnZSB3aGVuIGNnaSBpcyBkaXNwb3NlZC4KCgoJCXZvaWQgd3JpdGVUb0ZpbGUoc3RyaW5nIGZpbGVuYW1lVG9TYXZlVG8pIHsKCQkJaW1wb3J0IHN0ZC5maWxlOwoJCQlpZihjb250ZW50SW5NZW1vcnkpCgkJCQlzdGQuZmlsZS53cml0ZShmaWxlbmFtZVRvU2F2ZVRvLCBjb250ZW50KTsKCQkJZWxzZQoJCQkJc3RkLmZpbGUucmVuYW1lKGNvbnRlbnRGaWxlbmFtZSwgZmlsZW5hbWVUb1NhdmVUbyk7CgkJfQoJfQoKCS8vIGdpdmVuIGEgY29udGVudCB0eXBlIGFuZCBsZW5ndGgsIGRlY2lkZSB3aGF0IHdlJ3JlIGdvaW5nIHRvIGRvIHdpdGggdGhlIGRhdGEuLgoJcHJvdGVjdGVkIHZvaWQgcHJlcGFyZUZvckluY29taW5nRGF0YUNodW5rcyhzdHJpbmcgY29udGVudFR5cGUsIHVsb25nIGNvbnRlbnRMZW5ndGgpIHsKCQlwcHMuZXhwZWN0ZWRMZW5ndGggPSBjb250ZW50TGVuZ3RoOwoKCQlhdXRvIHRlcm1pbmF0b3IgPSBjb250ZW50VHlwZS5pbmRleE9mKCI7Iik7CgkJaWYodGVybWluYXRvciA9PSAtMSkKCQkJdGVybWluYXRvciA9IGNvbnRlbnRUeXBlLmxlbmd0aDsKCgkJcHBzLmNvbnRlbnRUeXBlID0gY29udGVudFR5cGVbMCAuLiB0ZXJtaW5hdG9yXTsKCQlhdXRvIGIgPSBjb250ZW50VHlwZVt0ZXJtaW5hdG9yIC4uICRdOwoJCWlmKGIubGVuZ3RoKSB7CgkJCWF1dG8gaWR4ID0gYi5pbmRleE9mKCJib3VuZGFyeT0iKTsKCQkJaWYoaWR4ICE9IC0xKSB7CgkJCQlwcHMuYm91bmRhcnkgPSBiW2lkeCArICJib3VuZGFyeT0iLmxlbmd0aCAuLiAkXTsKCQkJCXBwcy5sb2NhbEJvdW5kYXJ5ID0gIlxyXG4tLSIgfiBwcHMuYm91bmRhcnk7CgkJCX0KCQl9CgoJCS8vIHdoaWxlIGEgY29udGVudCB0eXBlIFNIT1VMRCBiZSBzZW50IGFjY29yZGluZyB0byB0aGUgUkZDLCBpdCBpcwoJCS8vIG5vdCByZXF1aXJlZC4gV2UncmUgdG9sZCB3ZSBTSE9VTEQgZ3Vlc3MgYnkgbG9va2luZyBhdCB0aGUgY29udGVudAoJCS8vIGJ1dCBpdCBzZWVtcyB0byBtZSB0aGF0IHRoaXMgb25seSBoYXBwZW5zIHdoZW4gaXQgaXMgdXJsZW5jb2RlZC4KCQlpZihwcHMuY29udGVudFR5cGUgPT0gImFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCIgfHwgcHBzLmNvbnRlbnRUeXBlID09ICIiKSB7CgkJCXBwcy5pc011bHRpcGFydCA9IGZhbHNlOwoJCX0gZWxzZSBpZihwcHMuY29udGVudFR5cGUgPT0gIm11bHRpcGFydC9mb3JtLWRhdGEiKSB7CgkJCXBwcy5pc011bHRpcGFydCA9IHRydWU7CgkJCWVuZm9yY2UocHBzLmJvdW5kYXJ5Lmxlbmd0aCwgIm5vIGJvdW5kYXJ5Iik7CgkJfSBlbHNlIGlmKHBwcy5jb250ZW50VHlwZSA9PSAiYXBwbGljYXRpb24vanNvbiIpIHsKCQkJcHBzLmlzSnNvbiA9IHRydWU7CgkJCXBwcy5pc011bHRpcGFydCA9IGZhbHNlOwoJCS8vfSBlbHNlIGlmKHBwcy5jb250ZW50VHlwZSA9PSAiYXBwbGljYXRpb24vanNvbiIpIHsKCQkJLy9wcHMuaXNKc29uID0gdHJ1ZTsKCQl9IGVsc2UgewoJCQkvLyBGSVhNRTogc2hvdWxkIHNldCBhIGh0dHAgZXJyb3IgY29kZSB0b28KCQkJdGhyb3cgbmV3IEV4Y2VwdGlvbigidW5rbm93biByZXF1ZXN0IGNvbnRlbnQgdHlwZTogIiB+IHBwcy5jb250ZW50VHlwZSk7CgkJfQoJfQoKCS8vIGhhbmRsZXMgc3RyZWFtaW5nIFBPU1QgZGF0YS4gSWYgeW91IGhhbmRsZSBzb21lIG90aGVyIGNvbnRlbnQgdHlwZSwgeW91IHNob3VsZAoJLy8gb3ZlcnJpZGUgdGhpcy4gSWYgdGhlIGRhdGEgaXNuJ3QgdGhlIGNvbnRlbnQgdHlwZSB5b3Ugd2FudCwgeW91IG91Z2h0IHRvIGNhbGwKCS8vIHN1cGVyLmhhbmRsZUluY29taW5nRGF0YUNodW5rIHNvIHJlZ3VsYXIgZm9ybXMgYW5kIGZpbGVzIHN0aWxsIHdvcmsuCgoJLy8gRklYTUU6IEkgZG8gc29tZSBjb3B5aW5nIGluIGhlcmUgdGhhdCBJJ20gcHJldHR5IHN1cmUgaXMgdW5uZWNlc3NhcnksIGFuZCB0aGUKCS8vIGZpbGUgc3R1ZmYgSSdtIHN1cmUgaXMgaW5lZmZpY2llbnQuIEJ1dCwgbXkgZ3Vlc3MgaXMgdGhlIHJlYWwgYm90dGxlbmVjayBpcyBuZXR3b3JrCgkvLyBpbnB1dCBhbnl3YXksIHNvIEknbSBub3QgZ29pbmcgdG8gZ2V0IHRvbyB3b3JrZWQgdXAgYWJvdXQgaXQgcmlnaHQgbm93LgoJcHJvdGVjdGVkIHZvaWQgaGFuZGxlSW5jb21pbmdEYXRhQ2h1bmsoY29uc3QodWJ5dGUpW10gY2h1bmspIHsKCQlpZihjaHVuay5sZW5ndGggPT0gMCkKCQkJcmV0dXJuOwoJCWFzc2VydChjaHVuay5sZW5ndGggPD0gMzIgKiAxMDI0ICogMTAyNCk7IC8vIHdlIHVzZSBjaHVuayBzaXplIGFzIGEgbWVtb3J5IGNvbnN0cmFpbnQgdGhpbmcsIHNvCgkJCQkJCQkvLyBpZiB3ZSdyZSBwYXNzZWQgYmlnIGNodW5rcywgaXQgbWlnaHQgdGhyb3cgdW5uZWNlc3NhcmlseS4KCQkJCQkJCS8vIGp1c3QgcGFzcyBpdCBzbWFsbGVyIGNodW5rcyBhdCBhIHRpbWUuCgkJaWYocHBzLmlzTXVsdGlwYXJ0KSB7CgkJCS8vIG11bHRpcGFydC9mb3JtLWRhdGEKCgoJCQl2b2lkIHBpZWNlSGFzTmV3Q29udGVudCgpIHsKCQkJCS8vIHdlIGp1c3QgZ3JldyB0aGUgcGllY2UncyBidWZmZXIuIERvIHdlIGhhdmUgdG8gc3dpdGNoIHRvIGZpbGUgYmFja2luZz8KCQkJCWlmKHBwcy5waWVjZS5jb250ZW50SW5NZW1vcnkpIHsKCQkJCQlpZihwcHMucGllY2UuY29udGVudC5sZW5ndGggPD0gMTAgKiAxMDI0ICogMTAyNCkKCQkJCQkJLy8gbWVoLCBJJ20gb2sgd2l0aCBpdC4KCQkJCQkJcmV0dXJuOwoJCQkJCWVsc2UgewoJCQkJCQkvLyB0aGlzIGlzIHRvbyBiaWcuCgkJCQkJCWlmKCFwcHMuaXNGaWxlKQoJCQkJCQkJdGhyb3cgbmV3IEV4Y2VwdGlvbigiUmVxdWVzdCBlbnRpdHkgdG9vIGxhcmdlIik7IC8vIGEgdmFyaWFibGUgdGhpcyBiaWcgaXMga2luZGEgcmlkaWN1bG91cywganVzdCByZWplY3QgaXQuCgkJCQkJCWVsc2UgewoJCQkJCQkJLy8gYSBmaWxlIHRoaXMgbGFyZ2UgaXMgcHJvYmFibHkgYWNjZXB0YWJsZSB0aG91Z2guLi4gbGV0J3MgdXNlIGEgYmFja2luZyBmaWxlLgoJCQkJCQkJcHBzLnBpZWNlLmNvbnRlbnRJbk1lbW9yeSA9IGZhbHNlOwoJCQkJCQkJLy8gRklYTUU6IHNheS4uLiBob3cgZG8gd2UgaW50ZW5kIHRvIGRlbGV0ZSB0aGVzZSB0aGluZ3M/IGNnaS5kaXNwb3NlIHBlcmhhcHMuCgoJCQkJCQkJaW50IGNvdW50ID0gMDsKCQkJCQkJCXBwcy5waWVjZS5jb250ZW50RmlsZW5hbWUgPSBnZXRUZW1wRGlyZWN0b3J5KCkgfiAiYXJzZF9jZ2lfdXBsb2FkZWRfZmlsZV8iIH4gdG8hc3RyaW5nKGdldFV0Y1RpbWUoKSkgfiAiLSIgfiB0byFzdHJpbmcoY291bnQpOwoJCQkJCQkJLy8gb2RkcyBhcmUgdGhpcyBsb29wIHdpbGwgbmV2ZXIgYmUgZW50ZXJlZCwgYnV0IHdlIHdhbnQgaXQganVzdCBpbiBjYXNlLgoJCQkJCQkJd2hpbGUoc3RkLmZpbGUuZXhpc3RzKHBwcy5waWVjZS5jb250ZW50RmlsZW5hbWUpKSB7CgkJCQkJCQkJY291bnQrKzsKCQkJCQkJCQlwcHMucGllY2UuY29udGVudEZpbGVuYW1lID0gZ2V0VGVtcERpcmVjdG9yeSgpIH4gImFyc2RfY2dpX3VwbG9hZGVkX2ZpbGVfIiB+IHRvIXN0cmluZyhnZXRVdGNUaW1lKCkpIH4gIi0iIH4gdG8hc3RyaW5nKGNvdW50KTsKCQkJCQkJCX0KCQkJCQkJCS8vIEkgaG9wZSB0aGlzIGNyZWF0ZXMgdGhlIGZpbGUgcHJldHR5IHF1aWNrbHksIG9yIHRoZSBsb29wIG1pZ2h0IGJlIHVzZWxlc3MuLi4KCQkJCQkJCS8vIEZJWE1FOiBtYXliZSBJIHNob3VsZCB3cml0ZSBzb21lIGtpbmQgb2YgY3VzdG9tIHRyYW5zYWN0aW9uIGhlcmUuCgkJCQkJCQlzdGQuZmlsZS53cml0ZShwcHMucGllY2UuY29udGVudEZpbGVuYW1lLCBwcHMucGllY2UuY29udGVudCk7CgoJCQkJCQkJcHBzLnBpZWNlLmNvbnRlbnQgPSBudWxsOwoJCQkJCQl9CgkJCQkJfQoJCQkJfSBlbHNlIHsKCQkJCQkvLyBpdCdzIGFscmVhZHkgaW4gYSBmaWxlLCBzbyBqdXN0IGFwcGVuZCBpdCB0byB3aGF0IHdlIGhhdmUKCQkJCQlpZihwcHMucGllY2UuY29udGVudC5sZW5ndGgpIHsKCQkJCQkJLy8gRklYTUU6IHRoaXMgaXMgc3VyZWx5IHZlcnkgaW5lZmZpY2llbnQuLi4gd2UnbGwgYmUgY2FsbGluZyB0aGlzIGJ5IDRrYiBjaHVuay4uLgoJCQkJCQlzdGQuZmlsZS5hcHBlbmQocHBzLnBpZWNlLmNvbnRlbnRGaWxlbmFtZSwgcHBzLnBpZWNlLmNvbnRlbnQpOwoJCQkJCQlwcHMucGllY2UuY29udGVudCA9IG51bGw7CgkJCQkJfQoJCQkJfQoJCQl9CgoKCQkJdm9pZCBjb21taXRQYXJ0KCkgewoJCQkJaWYoIXBwcy53ZUhhdmVBUGFydCkKCQkJCQlyZXR1cm47CgoJCQkJcGllY2VIYXNOZXdDb250ZW50KCk7IC8vIGJlIHN1cmUgdGhlIG5ldyBjb250ZW50IGlzIGhhbmRsZWQgZXZlcnkgdGltZQoKCQkJCWlmKHBwcy5pc0ZpbGUpIHsKCQkJCQkvLyBJJ20gbm90IHN1cmUgaWYgb3RoZXIgZW52aXJvbm1lbnRzIHB1dCBmaWxlcyBpbiBwb3N0IG9yIG5vdC4uLgoJCQkJCS8vIEkgdXNlZCB0byBub3QgZG8gaXQsIGJ1dCBJIHRoaW5rIEkgc2hvdWxkLCBzaW5jZSBpdCBpcyB0aGVyZS4uLgoJCQkJCXBwcy5fcG9zdFtwcHMucGllY2UubmFtZV0gfj0gcHBzLnBpZWNlLmZpbGVuYW1lOwoJCQkJCXBwcy5fZmlsZXNbcHBzLnBpZWNlLm5hbWVdIH49IHBwcy5waWVjZTsKCQkJCX0gZWxzZQoJCQkJCXBwcy5fcG9zdFtwcHMucGllY2UubmFtZV0gfj0gY2FzdChzdHJpbmcpIHBwcy5waWVjZS5jb250ZW50OwoKCQkJCS8qCgkJCQlzdGRlcnIud3JpdGVsbigiUkVDRUlWRUQ6ICIsIHBwcy5waWVjZS5uYW1lLCAiPSIsIAoJCQkJCXBwcy5waWVjZS5jb250ZW50Lmxlbmd0aCA8IDEwMDAKCQkJCQk/CgkJCQkJdG8hc3RyaW5nKHBwcy5waWVjZS5jb250ZW50KQoJCQkJCToKCQkJCQkidG9vIGxvbmciKTsKCQkJCSovCgoJCQkJLy8gRklYTUU6IHRoZSBsaW1pdCBoZXJlCgkJCQlwcHMubWVtb3J5Q29tbWl0dGVkICs9IHBwcy5waWVjZS5jb250ZW50Lmxlbmd0aDsKCgkJCQlwcHMud2VIYXZlQVBhcnQgPSBmYWxzZTsKCQkJCXBwcy53aGF0RG9XZVdhbnQgPSAxOwoJCQkJcHBzLnRoaXNPbmVzSGVhZGVycyA9IG51bGw7CgkJCQlwcHMudGhpc09uZXNEYXRhID0gbnVsbDsKCgkJCQlwcHMucGllY2UgPSBVcGxvYWRlZEZpbGUuaW5pdDsKCQkJCXBwcy5pc0ZpbGUgPSBmYWxzZTsKCQkJfQoKCQkJdm9pZCBhY2NlcHRDaHVuaygpIHsKCQkJCXBwcy5idWZmZXIgfj0gY2h1bms7CgkJCQljaHVuayA9IG51bGw7IC8vIHdlJ3ZlIGNvbnN1bWVkIGl0IGludG8gdGhlIGJ1ZmZlciwgc28ga2VlcGluZyBpdCBqdXN0IGJyaW5ncyBjb25mdXNpb24KCQkJfQoKCQkJaW1tdXRhYmxlKHVieXRlKVtdIGNvbnN1bWUoc2l6ZV90IGhvd011Y2gpIHsKCQkJCXBwcy5jb250ZW50Q29uc3VtZWQgKz0gaG93TXVjaDsKCQkJCWF1dG8gcmV0ID0gcHBzLmJ1ZmZlclswIC4uIGhvd011Y2hdOwoJCQkJcHBzLmJ1ZmZlciA9IHBwcy5idWZmZXJbaG93TXVjaCAuLiAkXTsKCQkJCXJldHVybiByZXQ7CgkJCX0KCgkJCWRhdGFDb25zdW1wdGlvbkxvb3A6IGRvIHsKCQkJc3dpdGNoKHBwcy53aGF0RG9XZVdhbnQpIHsKCQkJCWRlZmF1bHQ6IGFzc2VydCgwKTsKCQkJCWNhc2UgMDoKCQkJCQlhY2NlcHRDaHVuaygpOwoJCQkJCS8vIHRoZSBmb3JtYXQgYmVnaW5zIHdpdGggdHdvIGV4dHJhIGxlYWRpbmcgZGFzaGVzLCB0aGVuIHdlIHNob3VsZCBiZSBhdCB0aGUgYm91bmRhcnkKCQkJCQlpZihwcHMuYnVmZmVyLmxlbmd0aCA8IDIpCgkJCQkJCXJldHVybjsKCQkJCQlhc3NlcnQocHBzLmJ1ZmZlclswXSA9PSAnLScsICJubyBsZWFkaW5nIGRhc2giKTsKCQkJCQljb25zdW1lKDEpOwoJCQkJCWFzc2VydChwcHMuYnVmZmVyWzBdID09ICctJywgIm5vIHNlY29uZCBsZWFkaW5nIGRhc2giKTsKCQkJCQljb25zdW1lKDEpOwoKCQkJCQlwcHMud2hhdERvV2VXYW50ID0gMTsKCQkJCQlnb3RvIGNhc2UgMTsKCQkJCS8qIGZhbGx0aHJvdWdoICovCgkJCQljYXNlIDE6IC8vIGxvb2tpbmcgZm9yIGhlYWRlcnMKCQkJCQkvLyBoZXJlLCB3ZSBzaG91bGQgYmUgbGluZWQgdXAgcmlnaHQgYXQgdGhlIGJvdW5kYXJ5LCB3aGljaCBpcyBmb2xsb3dlZCBieSBhIFxyXG4KCgkJCQkJLy8gd2FudCB0byBrZWVwIHRoZSBidWZmZXIgdW5kZXIgY29udHJvbCBpbiBjYXNlIHdlJ3JlIHVuZGVyIGF0dGFjawoJCQkJCS8vc3RkZXJyLndyaXRlbG4oImhlcmUgb25jZSIpOwoJCQkJCS8vaWYocHBzLmJ1ZmZlci5sZW5ndGggKyBjaHVuay5sZW5ndGggPiA3MCAqIDEwMjQpIC8vIHRoZXkgc2hvdWxkIGJlIDwgMSBrYiByZWFsbHkuLi4uCgkJCQkJLy8JdGhyb3cgbmV3IEV4Y2VwdGlvbigid3RmIGlzIHVwIHdpdGggdGhlIGh1Z2UgbWltZSBwYXJ0IGhlYWRlcnMiKTsKCgkJCQkJYWNjZXB0Q2h1bmsoKTsKCgkJCQkJaWYocHBzLmJ1ZmZlci5sZW5ndGggPCBwcHMuYm91bmRhcnkubGVuZ3RoKQoJCQkJCQlyZXR1cm47IC8vIG5vdCBlbm91Z2ggZGF0YSwgc2luY2UgdGhlcmUgc2hvdWxkIGFsd2F5cyBiZSBhIGJvdW5kYXJ5IGhlcmUgYXQgbGVhc3QKCgkJCQkJaWYocHBzLmNvbnRlbnRDb25zdW1lZCArIHBwcy5ib3VuZGFyeS5sZW5ndGggKyA2ID09IHBwcy5leHBlY3RlZExlbmd0aCkgewoJCQkJCQlhc3NlcnQocHBzLmJ1ZmZlci5sZW5ndGggPT0gcHBzLmJvdW5kYXJ5Lmxlbmd0aCArIDQgKyAyKTsgLy8gLS0sIC0tLCBhbmQgXHJcbgoJCQkJCQkvLyB3ZSAqc2hvdWxkKiBiZSBhdCB0aGUgZW5kIGhlcmUhCgkJCQkJCWFzc2VydChwcHMuYnVmZmVyWzBdID09ICctJyk7CgkJCQkJCWNvbnN1bWUoMSk7CgkJCQkJCWFzc2VydChwcHMuYnVmZmVyWzBdID09ICctJyk7CgkJCQkJCWNvbnN1bWUoMSk7CgoJCQkJCQkvLyB0aGUgbWVzc2FnZSBpcyB0ZXJtaW5hdGVkIGJ5IC0tQk9VTkRBUlktLVxyXG4gKGFmdGVyIGEgXHJcbiBsZWFkaW5nIHRvIHRoZSBib3VuZGFyeSkKCQkJCQkJYXNzZXJ0KHBwcy5idWZmZXJbMCAuLiBwcHMuYm91bmRhcnkubGVuZ3RoXSA9PSBjYXN0KGNvbnN0KHVieXRlW10pKSBwcHMuYm91bmRhcnksCgkJCQkJCQkibm90IGxpbmVkIHVwIG9uIGJvdW5kYXJ5ICIgfiBwcHMuYm91bmRhcnkpOwoJCQkJCQljb25zdW1lKHBwcy5ib3VuZGFyeS5sZW5ndGgpOwoKCQkJCQkJYXNzZXJ0KHBwcy5idWZmZXJbMF0gPT0gJy0nKTsKCQkJCQkJY29uc3VtZSgxKTsKCQkJCQkJYXNzZXJ0KHBwcy5idWZmZXJbMF0gPT0gJy0nKTsKCQkJCQkJY29uc3VtZSgxKTsKCgkJCQkJCWFzc2VydChwcHMuYnVmZmVyWzBdID09ICdccicpOwoJCQkJCQljb25zdW1lKDEpOwoJCQkJCQlhc3NlcnQocHBzLmJ1ZmZlclswXSA9PSAnXG4nKTsKCQkJCQkJY29uc3VtZSgxKTsKCgkJCQkJCWFzc2VydChwcHMuYnVmZmVyLmxlbmd0aCA9PSAwKTsKCQkJCQkJYXNzZXJ0KHBwcy5jb250ZW50Q29uc3VtZWQgPT0gcHBzLmV4cGVjdGVkTGVuZ3RoKTsKCQkJCQkJYnJlYWsgZGF0YUNvbnN1bXB0aW9uTG9vcDsgLy8gd2UncmUgZG9uZSEKCQkJCQl9IGVsc2UgewoJCQkJCQkvLyB3ZSdyZSBub3QgZG9uZSB5ZXQuIFdlIHNob3VsZCBiZSBsaW5lZCB1cCBvbiBhIGJvdW5kYXJ5LgoKCQkJCQkJLy8gQnV0LCB3ZSB3YW50IHRvIGVuc3VyZSB0aGUgaGVhZGVycyBhcmUgaGVyZSBiZWZvcmUgd2UgY29uc3VtZSBhbnl0aGluZyEKCQkJCQkJYXV0byBoZWFkZXJFbmRMb2NhdGlvbiA9IGxvY2F0aW9uT2YocHBzLmJ1ZmZlciwgIlxyXG5cclxuIik7CgkJCQkJCWlmKGhlYWRlckVuZExvY2F0aW9uID09IC0xKQoJCQkJCQkJcmV0dXJuOyAvLyB0aGV5ICpzaG91bGQqIGFsbCBiZSBoZXJlLCBzbyB3ZSBjYW4gaGFuZGxlIHRoZW0gYWxsIGF0IG9uY2UuCgoJCQkJCQlhc3NlcnQocHBzLmJ1ZmZlclswIC4uIHBwcy5ib3VuZGFyeS5sZW5ndGhdID09IGNhc3QoY29uc3QodWJ5dGVbXSkpIHBwcy5ib3VuZGFyeSwKCQkJCQkJCSJub3QgbGluZWQgdXAgb24gYm91bmRhcnkgIiB+IHBwcy5ib3VuZGFyeSk7CgoJCQkJCQljb25zdW1lKHBwcy5ib3VuZGFyeS5sZW5ndGgpOwoJCQkJCQkvLyB0aGUgYm91bmRhcnkgaXMgYWx3YXlzIGZvbGxvd2VkIGJ5IGEgXHJcbgoJCQkJCQlhc3NlcnQocHBzLmJ1ZmZlclswXSA9PSAnXHInKTsKCQkJCQkJY29uc3VtZSgxKTsKCQkJCQkJYXNzZXJ0KHBwcy5idWZmZXJbMF0gPT0gJ1xuJyk7CgkJCQkJCWNvbnN1bWUoMSk7CgkJCQkJfQoKCQkJCQkvLyByZS1ydW5uaW5nIHNpbmNlIGJ5IGNvbnN1bWluZyB0aGUgYm91bmRhcnksIHdlIGludmFsaWRhdGUgdGhlIG9sZCBpbmRleC4KCQkJCQlhdXRvIGhlYWRlckVuZExvY2F0aW9uID0gbG9jYXRpb25PZihwcHMuYnVmZmVyLCAiXHJcblxyXG4iKTsKCQkJCQlhc3NlcnQoaGVhZGVyRW5kTG9jYXRpb24gPj0gMCwgIm5vIGhlYWRlciIpOwoJCQkJCWF1dG8gdGhpc09uZXNIZWFkZXJzID0gcHBzLmJ1ZmZlclswLi5oZWFkZXJFbmRMb2NhdGlvbl07CgoJCQkJCWNvbnN1bWUoaGVhZGVyRW5kTG9jYXRpb24gKyA0KTsgLy8gVGhlICs0IGlzIHRoZSBcclxuXHJcbiB0aGF0IGNhcHMgaXQgb2ZmCgoJCQkJCXBwcy50aGlzT25lc0hlYWRlcnMgPSBzcGxpdChjYXN0KHN0cmluZykgdGhpc09uZXNIZWFkZXJzLCAiXHJcbiIpOwoKCQkJCQkvLyBub3cgd2UnbGwgcGFyc2UgdGhlIGhlYWRlcnMKCQkJCQlmb3JlYWNoKGg7IHBwcy50aGlzT25lc0hlYWRlcnMpIHsKCQkJCQkJYXV0byBwID0gaC5pbmRleE9mKCI6Iik7CgkJCQkJCWFzc2VydChwICE9IC0xLCAibm8gY29sb24gaW4gaGVhZGVyLCBnb3QgIiB+IHRvIXN0cmluZyhwcHMudGhpc09uZXNIZWFkZXJzKSk7CgkJCQkJCXN0cmluZyBobiA9IGhbMC4ucF07CgkJCQkJCXN0cmluZyBodiA9IGhbcCsyLi4kXTsKCgkJCQkJCXN3aXRjaChobi50b0xvd2VyKSB7CgkJCQkJCQlkZWZhdWx0OiBhc3NlcnQoMCk7CgkJCQkJCQljYXNlICJjb250ZW50LWRpc3Bvc2l0aW9uIjoKCQkJCQkJCQlhdXRvIGluZm8gPSBodi5zcGxpdCgiOyAiKTsKCQkJCQkJCQlmb3JlYWNoKGk7IGluZm9bMS4uJF0pIHsgLy8gc2tpcHBpbmcgdGhlIGZvcm0tZGF0YQoJCQkJCQkJCQlhdXRvIG8gPSBpLnNwbGl0KCI9Iik7IC8vIEZJWE1FCgkJCQkJCQkJCXN0cmluZyBwbiA9IG9bMF07CgkJCQkJCQkJCXN0cmluZyBwdiA9IG9bMV1bMS4uJC0xXTsKCgkJCQkJCQkJCWlmKHBuID09ICJuYW1lIikgewoJCQkJCQkJCQkJcHBzLnBpZWNlLm5hbWUgPSBwdjsKCQkJCQkJCQkJfSBlbHNlIGlmIChwbiA9PSAiZmlsZW5hbWUiKSB7CgkJCQkJCQkJCQlwcHMucGllY2UuZmlsZW5hbWUgPSBwdjsKCQkJCQkJCQkJCXBwcy5pc0ZpbGUgPSB0cnVlOwoJCQkJCQkJCQl9CgkJCQkJCQkJfQoJCQkJCQkJYnJlYWs7CgkJCQkJCQljYXNlICJjb250ZW50LXR5cGUiOgoJCQkJCQkJCXBwcy5waWVjZS5jb250ZW50VHlwZSA9IGh2OwoJCQkJCQkJYnJlYWs7CgkJCQkJCX0KCQkJCQl9CgoJCQkJCXBwcy53aGF0RG9XZVdhbnQrKzsgLy8gbW92ZSB0byB0aGUgbmV4dCBzdGVwIC0gdGhlIGRhdGEKCQkJCWJyZWFrOwoJCQkJY2FzZSAyOgoJCQkJCS8vIHdoZW4gd2UgZ2V0IGhlcmUsIHBwcy5idWZmZXIgc2hvdWxkIGNvbnRhaW4gb3VyIGZpcnN0IGNodW5rIG9mIGRhdGEKCgkJCQkJaWYocHBzLmJ1ZmZlci5sZW5ndGggKyBjaHVuay5sZW5ndGggPiA4ICogMTAyNCAqIDEwMjQpIC8vIHdlIG1pZ2h0IGJ1ZmZlciBxdWl0ZSBhIGJpdCBidXQgbm90IG11Y2gKCQkJCQkJdGhyb3cgbmV3IEV4Y2VwdGlvbigid3RmIGlzIHVwIHdpdGggdGhlIGh1Z2UgbWltZSBwYXJ0IGJ1ZmZlciIpOwoKCQkJCQlhY2NlcHRDaHVuaygpOwoKCQkJCQkvLyBzbyB0aGUgdHJpY2sgaXMsIHdlIHdhbnQgdG8gcHJvY2VzcyBhbGwgdGhlIGRhdGEgdXAgdG8gdGhlIGJvdW5kYXJ5LAoJCQkJCS8vIGJ1dCB3aGF0IGlmIHRoZSBjaHVuaydzIGVuZCBjdXRzIHRoZSBib3VuZGFyeSBvZmY/IElmIHdlJ3JlIHVuc3VyZSwgd2UKCQkJCQkvLyB3YW50IHRvIHdhaXQgZm9yIHRoZSBuZXh0IGNodW5rLiBXZSBzdGFydCBieSBsb29raW5nIGZvciB0aGUgd2hvbGUgYm91bmRhcnkKCQkJCQkvLyBpbiB0aGUgYnVmZmVyIHNvbWV3aGVyZS4KCgkJCQkJYXV0byBib3VuZGFyeUxvY2F0aW9uID0gbG9jYXRpb25PZihwcHMuYnVmZmVyLCBwcHMubG9jYWxCb3VuZGFyeSk7CgkJCQkJLy8gYXNzZXJ0KGJvdW5kYXJ5TG9jYXRpb24gIT0gLTEsICJzaG91bGQgaGF2ZSBzZWVuICJ+dG8hc3RyaW5nKGNhc3QodWJ5dGVbXSkgcHBzLmxvY2FsQm91bmRhcnkpfiIgaW4gIiB+IHRvIXN0cmluZyhwcHMuYnVmZmVyKSk7CgkJCQkJaWYoYm91bmRhcnlMb2NhdGlvbiAhPSAtMSkgewoJCQkJCQkvLyB0aGlzIGlzIGVhc3kgLSB3ZSBjYW4gc2VlIGl0IGluIGl0J3MgZW50aXJldHkhCgoJCQkJCQlwcHMucGllY2UuY29udGVudCB+PSBjb25zdW1lKGJvdW5kYXJ5TG9jYXRpb24pOwoKCQkJCQkJYXNzZXJ0KHBwcy5idWZmZXJbMF0gPT0gJ1xyJyk7CgkJCQkJCWNvbnN1bWUoMSk7CgkJCQkJCWFzc2VydChwcHMuYnVmZmVyWzBdID09ICdcbicpOwoJCQkJCQljb25zdW1lKDEpOwoJCQkJCQlhc3NlcnQocHBzLmJ1ZmZlclswXSA9PSAnLScpOwoJCQkJCQljb25zdW1lKDEpOwoJCQkJCQlhc3NlcnQocHBzLmJ1ZmZlclswXSA9PSAnLScpOwoJCQkJCQljb25zdW1lKDEpOwoJCQkJCQkvLyB0aGUgYm91bmRhcnkgaGVyZSBpcyBhbHdheXMgcHJlY2VkZWQgYnkgXHJcbi0tLCB3aGljaCBpcyB3aHkgd2UgdXNlZCBsb2NhbEJvdW5kYXJ5IGluc3RlYWQgb2YgYm91bmRhcnkgdG8gbG9jYXRlIGl0LiBDdXQgdGhhdCBvZmYuCgkJCQkJCXBwcy53ZUhhdmVBUGFydCA9IHRydWU7CgkJCQkJCXBwcy53aGF0RG9XZVdhbnQgPSAxOyAvLyBiYWNrIHRvIGdldHRpbmcgaGVhZGVycyBmb3IgdGhlIG5leHQgcGFydAoKCQkJCQkJY29tbWl0UGFydCgpOyAvLyB3ZSdyZSBkb25lIGhlcmUKCQkJCQl9IGVsc2UgewoJCQkJCQkvLyB3ZSBjYW4ndCBzZWUgdGhlIHdob2xlIHRoaW5nLCBidXQgd2hhdCBpZiB0aGVyZSdzIGEgcGFydGlhbCBib3VuZGFyeT8KCgkJCQkJCWVuZm9yY2UocHBzLmxvY2FsQm91bmRhcnkubGVuZ3RoIDwgMTI4KTsgLy8gdGhlIGJvdW5kYXJ5IG91Z2h0IHRvIGJlIGxlc3MgdGhhbiBhIGxpbmUuLi4KCQkJCQkJYXNzZXJ0KHBwcy5sb2NhbEJvdW5kYXJ5Lmxlbmd0aCA+IDEpOyAvLyBzaG91bGQgYWxyZWFkeSBiZSBzYW5lIGJ1dCBqdXN0IGluIGNhc2UKCQkJCQkJYm9vbCBwb3RlbnRpYWxCb3VuZGFyeUZvdW5kID0gZmFsc2U7CgoJCQkJCQlib3VuZGFyeUNoZWNrOiBmb3IoaW50IGEgPSAxOyBhIDwgcHBzLmxvY2FsQm91bmRhcnkubGVuZ3RoOyBhKyspIHsKCQkJCQkJCS8vIHdlIGdyb3cgdGhlIGJvdW5kYXJ5IGEgYml0IGVhY2ggdGltZS4gSWYgd2UgdGhpbmsgaXQgbG9va3MgdGhlCgkJCQkJCQkvLyBzYW1lLCBiZXR0ZXIgcHVsbCBhbm90aGVyIGNodW5rIHRvIGJlIHN1cmUgaXQncyBub3QgdGhlIGVuZC4KCQkJCQkJCS8vIFN0YXJ0aW5nIHNtYWxsIGJlY2F1c2UgZXhpdGluZyB0aGUgbG9vcCBlYXJseSBpcyBkZXNpcmFibGUsIHNpbmNlCgkJCQkJCQkvLyB3ZSdyZSBub3Qga2VlcGluZyBhbnkgYW1iaWd1aXR5IGFuZCAxIC8gMjU2IGNoYW5jZSBvZiBleGl0aW5nIGlzCgkJCQkJCQkvLyB0aGUgYmVzdCB3ZSBjYW4gZG8uCgkJCQkJCQlpZihhID4gcHBzLmJ1ZmZlci5sZW5ndGgpCgkJCQkJCQkJYnJlYWs7IC8vIEZJWE1FOiBpcyB0aGlzIHJpZ2h0PwoJCQkJCQkJYXNzZXJ0KGEgPD0gcHBzLmJ1ZmZlci5sZW5ndGgpOwoJCQkJCQkJYXNzZXJ0KGEgPiAwKTsKCQkJCQkJCWlmKHN0ZC5hbGdvcml0aG0uZW5kc1dpdGgocHBzLmJ1ZmZlciwgcHBzLmxvY2FsQm91bmRhcnlbMCAuLiBhXSkpIHsKCQkJCQkJCQkvLyBvaywgdGhlcmUgKm1pZ2h0KiBiZSBhIGJvdW5kYXJ5IGhlcmUsIHNvIGxldCdzCgkJCQkJCQkJLy8gbm90IHRyZWF0IHRoZSBlbmQgYXMgZGF0YSB5ZXQuIFRoZSByZXN0IGlzIGdvb2QgdG8KCQkJCQkJCQkvLyB1c2UgdGhvdWdoLCBzaW5jZSBpZiB0aGVyZSB3YXMgYSBib3VuZGFyeSB0aGVyZSwgd2UnZAoJCQkJCQkJCS8vIGhhdmUgaGFuZGxlZCBpdCB1cCBhYm92ZSBhZnRlciBsb2NhdGlvbk9mLgoKCQkJCQkJCQlwcHMucGllY2UuY29udGVudCB+PSBwcHMuYnVmZmVyWzAgLi4gJCAtIGFdOwoJCQkJCQkJCWNvbnN1bWUocHBzLmJ1ZmZlci5sZW5ndGggLSBhKTsKCQkJCQkJCQlwaWVjZUhhc05ld0NvbnRlbnQoKTsKCQkJCQkJCQlwb3RlbnRpYWxCb3VuZGFyeUZvdW5kID0gdHJ1ZTsKCQkJCQkJCQlicmVhayBib3VuZGFyeUNoZWNrOwoJCQkJCQkJfQoJCQkJCQl9CgoJCQkJCQlpZighcG90ZW50aWFsQm91bmRhcnlGb3VuZCkgewoJCQkJCQkJLy8gd2UgY2FuIGNvbnN1bWUgdGhlIHdob2xlIHRoaW5nCgkJCQkJCQlwcHMucGllY2UuY29udGVudCB+PSBwcHMuYnVmZmVyOwoJCQkJCQkJcGllY2VIYXNOZXdDb250ZW50KCk7CgkJCQkJCQljb25zdW1lKHBwcy5idWZmZXIubGVuZ3RoKTsKCQkJCQkJfSBlbHNlIHsKCQkJCQkJCS8vIHdlIGZvdW5kIGEgcG9zc2libGUgYm91bmRhcnksIGJ1dCB0aGVyZSB3YXMKCQkJCQkJCS8vIGluc3VmZmljaWVudCBkYXRhIHRvIGJlIHN1cmUuCgkJCQkJCQlhc3NlcnQocHBzLmJ1ZmZlciA9PSBjYXN0KGNvbnN0KHVieXRlW10pKSBwcHMubG9jYWxCb3VuZGFyeVswIC4uIHBwcy5idWZmZXIubGVuZ3RoXSk7CgoJCQkJCQkJcmV0dXJuOyAvLyB3YWl0IGZvciB0aGUgbmV4dCBjaHVuay4KCQkJCQkJfQoJCQkJCX0KCQkJfQoJCQl9IHdoaWxlKHBwcy5idWZmZXIubGVuZ3RoKTsKCgkJCS8vIGJ0dyBhbGwgYm91bmRhcmllcyBleGNlcHQgdGhlIGZpcnN0IHNob3VsZCBoYXZlIGEgXHJcbiBiZWZvcmUgdGhlbQoJCX0gZWxzZSB7CgkJCS8vIGFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCBhbmQgYXBwbGljYXRpb24vanNvbgoKCQkJCS8vIG5vdCB1c2luZyBtYXhDb250ZW50TGVuZ3RoIGJlY2F1c2UgdGhhdCBtaWdodCBiZSBjcmFua2VkIHVwIHRvIGFsbG93CgkJCQkvLyBsYXJnZSBmaWxlIHVwbG9hZHMuIFdlIGNhbiBoYW5kbGUgdGhlbSwgYnV0IGEgaHVnZSBwb3N0W10gaXNuJ3QgYW55IGdvb2QuCgkJCWlmKHBwcy5idWZmZXIubGVuZ3RoICsgY2h1bmsubGVuZ3RoID4gOCAqIDEwMjQgKiAxMDI0KSAvLyBzdXJlbHkgdGhpcyBpcyBwbGVudHkgYmlnIGVub3VnaAoJCQkJdGhyb3cgbmV3IEV4Y2VwdGlvbigid3RmIGlzIHVwIHdpdGggc3VjaCBhIGdpZ2FudGljIGZvcm0gc3VibWlzc2lvbj8/Pz8iKTsKCgkJCXBwcy5idWZmZXIgfj0gY2h1bms7CgoJCQkvLyBzaW1wbGUgaGFuZGxpbmcsIGJ1dCBpdCB3b3Jrcy4uLiB1bnRpbCBzb21lb25lIGJvbWJzIHVzIHdpdGggZ2lnYWJ5dGVzIG9mIGNyYXAgYXQgbGVhc3QuLi4KCQkJaWYocHBzLmJ1ZmZlci5sZW5ndGggPT0gcHBzLmV4cGVjdGVkTGVuZ3RoKSB7CgkJCQlpZihwcHMuaXNKc29uKQoJCQkJCXBwcy5wb3N0SnNvbiA9IGNhc3Qoc3RyaW5nKSBwcHMuYnVmZmVyOwoJCQkJZWxzZQoJCQkJCXBwcy5fcG9zdCA9IGRlY29kZVZhcmlhYmxlcyhjYXN0KHN0cmluZykgcHBzLmJ1ZmZlcik7CgkJCQl2ZXJzaW9uKHByZXNlcnZlRGF0YSkKCQkJCQlvcmlnaW5hbFBvc3REYXRhID0gcHBzLmJ1ZmZlcjsKCQkJfSBlbHNlIHsKCQkJCS8vIGp1c3QgZm9yIGRlYnVnZ2luZwoJCQl9CgkJfQoJfQoKCXByb3RlY3RlZCB2b2lkIGNsZWFuVXBQb3N0RGF0YVN0YXRlKCkgewoJCXBwcyA9IFBvc3RQYXJzZXJTdGF0ZS5pbml0OwoJfQoKCS8vLyB5b3UgY2FuIG92ZXJyaWRlIHRoaXMgZnVuY3Rpb24gdG8gc29tZWhvdyByZWFjdAoJLy8vIHRvIGFuIHVwbG9hZCBpbiBwcm9ncmVzcy4KCS8vLwoJLy8vIFRha2Ugbm90ZSB0aGF0IHBhcnRzIG9mIHRoZSBDR0kgb2JqZWN0IGlzIG5vdCB5ZXQKCS8vLyBpbml0aWFsaXplZCEgU3R1ZmYgZnJvbSBIVFRQIGhlYWRlcnMsIGluY2x1ZGluZyBnZXRbXSwgaXMgdXNhYmxlLgoJLy8vIEJ1dCwgbm9uZSBvZiBwb3N0W10gaXMgdXNhYmxlLCBhbmQgeW91IGNhbm5vdCB3cml0ZSBoZXJlLiBUaGF0J3MKCS8vLyB3aHkgdGhpcyBtZXRob2QgaXMgY29uc3QgLSBtdXRhdGluZyB0aGUgb2JqZWN0IHdvbid0IGRvIG11Y2ggYW55d2F5LgoJLy8vCgkvLy8gTXkgaWRlYSBoZXJlIHdhcyBzbyB5b3UgY2FuIG91dHB1dCBhIHByb2dyZXNzIGJhciBvcgoJLy8vIHNvbWV0aGluZyB0byBhIGNvb3BlcmF0aXZlIGNsaWVudCAoc2VlIGFyc2QucnR1ZCBmb3IgYSBwb3RlbnRpYWwgaGVscGVyKQoJLy8vCgkvLy8gVGhlIGRlZmF1bHQgaXMgdG8gZG8gbm90aGluZy4gU3ViY2xhc3MgY2dpIGFuZCB1c2UgdGhlIAoJLy8vIEN1c3RvbUNnaU1haW4gbWl4aW4gdG8gZG8gc29tZXRoaW5nIGhlcmUuCgl2b2lkIG9uUmVxdWVzdEJvZHlEYXRhUmVjZWl2ZWQoc2l6ZV90IHJlY2VpdmVkU29GYXIsIHNpemVfdCB0b3RhbEV4cGVjdGVkKSBjb25zdCB7CgkJLy8gVGhpcyBzcGFjZSBpbnRlbnRpb25hbGx5IGxlZnQgYmxhbmsuCgl9CgoJLy8vIEluaXRpYWxpemVzIHRoZSBjZ2kgZnJvbSBjb21wbGV0ZWx5IHJhdyBIVFRQIGRhdGEuIFRoZSBpciBtdXN0IGhhdmUgYSBTb2NrZXQgc291cmNlLgoJLy8vICpjbG9zZUNvbm5lY3Rpb24gd2lsbCBiZSBzZXQgdG8gdHJ1ZSBpZiB5b3Ugc2hvdWxkIGNsb3NlIHRoZSBjb25uZWN0aW9uIGFmdGVyIGhhbmRsaW5nIHRoaXMgcmVxdWVzdAoJdGhpcyhCdWZmZXJlZElucHV0UmFuZ2UgaXIsIGJvb2wqIGNsb3NlQ29ubmVjdGlvbikgewoJCWlzQ2FsbGVkV2l0aENvbW1hbmRMaW5lQXJndW1lbnRzID0gZmFsc2U7CgkJaW1wb3J0IGFsID0gc3RkLmFsZ29yaXRobTsKCgkJaW1tdXRhYmxlKHVieXRlKVtdIGRhdGE7CgoJCXZvaWQgcmRvKGNvbnN0KHVieXRlKVtdIGQpIHsKCQkJc2VuZEFsbChpci5zb3VyY2UsIGQpOwoJCX0KCgkJdGhpcyhpciwgaXIuc291cmNlLnJlbW90ZUFkZHJlc3MoKS50b1N0cmluZygpLCA4MCAvKiBGSVhNRSAqLywgMCwgZmFsc2UsICZyZG8sIG51bGwsIGNsb3NlQ29ubmVjdGlvbik7Cgl9CgoJLyoqCgkJSW5pdGlhbGl6ZXMgaXQgZnJvbSByYXcgSFRUUCByZXF1ZXN0IGRhdGEuIEdlbmVyaWNNYWluIHVzZXMgdGhpcyB3aGVuIHlvdSBjb21waWxlIHdpdGggLXZlcnNpb249ZW1iZWRkZWRfaHR0cGQuCgoJCU5PVEU6IElmIHlvdSBhcmUgYmVoaW5kIGEgcmV2ZXJzZSBwcm94eSwgdGhlIHZhbHVlcyBoZXJlIG1pZ2h0IG5vdCBiZSB3aGF0IHlvdSBleHBlY3QuLi4uIGl0IHdpbGwgdXNlIFgtRm9yd2FyZGVkLUZvciBmb3IgcmVtb3RlIElQIGFuZCBYLUZvcndhcmRlZC1Ib3N0IGZvciBob3N0CgoJCVBhcmFtczoKCQkJaW5wdXREYXRhID0gdGhlIGluY29taW5nIGRhdGEsIGluY2x1ZGluZyBoZWFkZXJzIGFuZCBvdGhlciByYXcgaHR0cCBkYXRhLgoJCQkJV2hlbiB0aGUgY29uc3RydWN0b3IgZXhpdHMsIGl0IHdpbGwgbGVhdmUgdGhpcyByYW5nZSBleGFjdGx5IGF0IHRoZSBzdGFydCBvZgoJCQkJdGhlIG5leHQgcmVxdWVzdCBvbiB0aGUgY29ubmVjdGlvbiAoaWYgdGhlcmUgaXMgb25lKS4KCgkJCWFkZHJlc3MgPSB0aGUgSVAgYWRkcmVzcyBvZiB0aGUgcmVtb3RlIHVzZXIKCQkJX3BvcnQgPSB0aGUgcG9ydCBudW1iZXIgb2YgdGhlIGNvbm5lY3Rpb24KCQkJcGF0aEluZm9TdGFydHMgPSB0aGUgb2Zmc2V0IGludG8gdGhlIHBhdGggY29tcG9uZW50IG9mIHRoZSBodHRwIGhlYWRlciB3aGVyZSB0aGUgU0NSSVBUX05BTUUgZW5kcyBhbmQgdGhlIFBBVEhfSU5GTyBiZWdpbnMuCgkJCV9odHRwcyA9IGlmIHRoaXMgY29ubmVjdGlvbiBpcyBlbmNyeXB0ZWQgKG5vdGUgdGhhdCB0aGUgaW5wdXQgZGF0YSBtdXN0IG5vdCBhY3R1YWxseSBiZSBlbmNyeXB0ZWQpCgkJCV9yYXdEYXRhT3V0cHV0ID0gZGVsZWdhdGUgdG8gYWNjZXB0IHJlc3BvbnNlIGRhdGEuIEl0IHNob3VsZCB3cml0ZSB0byB0aGUgc29ja2V0IG9yIHdoYXRldmVyOyBDZ2kgZG9lcyBhbGwgdGhlIG5lZWRlZCBwcm9jZXNzaW5nIHRvIHNwZWFrIGh0dHAuCgkJCV9mbHVzaCA9IGlmIF9yYXdEYXRhT3V0cHV0IGJ1ZmZlcnMsIHRoaXMgZGVsZWdhdGUgc2hvdWxkIGZsdXNoIHRoZSBidWZmZXIgZG93biB0aGUgd2lyZQoJCQljbG9zZUNvbm5lY3Rpb24gPSBpZiB0aGUgcmVxdWVzdCBhc2tzIHRvIGNsb3NlIHRoZSBjb25uZWN0aW9uLCAqY2xvc2VDb25uZWN0aW9uID09IHRydWUuCgkqLwoJdGhpcygKCQlCdWZmZXJlZElucHV0UmFuZ2UgaW5wdXREYXRhLAovLwkJc3RyaW5nW10gaGVhZGVycywgaW1tdXRhYmxlKHVieXRlKVtdIGRhdGEsCgkJc3RyaW5nIGFkZHJlc3MsIHVzaG9ydCBfcG9ydCwKCQlpbnQgcGF0aEluZm9TdGFydHMgPSAwLCAvLyB1c2UgdGhpcyBpZiB5b3Uga25vdyB0aGUgc2NyaXB0IG5hbWUsIGxpa2UgaWYgdGhpcyBpcyBpbiBhIGZvbGRlciBpbiBhIGJpZ2dlciB3ZWIgZW52aXJvbm1lbnQKCQlib29sIF9odHRwcyA9IGZhbHNlLAoJCXZvaWQgZGVsZWdhdGUoY29uc3QodWJ5dGUpW10pIF9yYXdEYXRhT3V0cHV0ID0gbnVsbCwKCQl2b2lkIGRlbGVnYXRlKCkgX2ZsdXNoID0gbnVsbCwKCQkvLyB0aGlzIHBvaW50ZXIgdGVsbHMgaWYgdGhlIGNvbm5lY3Rpb24gaXMgc3VwcG9zZWQgdG8gYmUgY2xvc2VkIGFmdGVyIHdlIGhhbmRsZSB0aGlzCgkJYm9vbCogY2xvc2VDb25uZWN0aW9uID0gbnVsbCkKCXsKCQkvLyB0aGVzZSBhcmUgYWxsIHNldCBsb2NhbGx5IHNvIHRoZSBsb29wIHdvcmtzCgkJLy8gd2l0aG91dCB0cmlnZ2VyaW5nIGVycm9ycyBpbiBkbWQgMi4wNjQKCQkvLyB3ZSBnbyBhaGVhZCBhbmQgc2V0IHRoZW0gYXQgdGhlIGVuZCBvZiBpdCB0byB0aGUgdGhpcyB2ZXJzaW9uCgkJaW50IHBvcnQ7CgkJc3RyaW5nIHJlZmVycmVyOwoJCXN0cmluZyByZW1vdGVBZGRyZXNzOwoJCXN0cmluZyB1c2VyQWdlbnQ7CgkJc3RyaW5nIGF1dGhvcml6YXRpb247CgkJc3RyaW5nIG9yaWdpbjsKCQlzdHJpbmcgYWNjZXB0OwoJCXN0cmluZyBsYXN0RXZlbnRJZDsKCQlib29sIGh0dHBzOwoJCXN0cmluZyBob3N0OwoJCVJlcXVlc3RNZXRob2QgcmVxdWVzdE1ldGhvZDsKCQlzdHJpbmcgcmVxdWVzdFVyaTsKCQlzdHJpbmcgcGF0aEluZm87CgkJc3RyaW5nIHF1ZXJ5U3RyaW5nOwoJCXN0cmluZyBzY3JpcHROYW1lOwoJCXN0cmluZ1tzdHJpbmddIGdldDsKCQlzdHJpbmdbXVtzdHJpbmddIGdldEFycmF5OwoJCWJvb2wga2VlcEFsaXZlUmVxdWVzdGVkOwoJCWJvb2wgYWNjZXB0c0d6aXA7CgkJc3RyaW5nIGNvb2tpZTsKCgoKCQllbnZpcm9ubWVudFZhcmlhYmxlcyA9IGNhc3QoY29uc3QpIGVudmlyb25tZW50LnRvQUE7CgoJCWlkbG9sID0gaW5wdXREYXRhOwoKCQlpc0NhbGxlZFdpdGhDb21tYW5kTGluZUFyZ3VtZW50cyA9IGZhbHNlOwoKCQlodHRwcyA9IF9odHRwczsKCQlwb3J0ID0gX3BvcnQ7CgoJCXJhd0RhdGFPdXRwdXQgPSBfcmF3RGF0YU91dHB1dDsKCQlmbHVzaERlbGVnYXRlID0gX2ZsdXNoOwoJCW5waCA9IHRydWU7CgoJCXJlbW90ZUFkZHJlc3MgPSBhZGRyZXNzOwoKCQkvLyBzdHJlYW1pbmcgcGFyc2VyCgkJaW1wb3J0IGFsID0gc3RkLmFsZ29yaXRobTsKCgkJCS8vIEZJWE1FOiB0aXMgY2FzdCBpcyB0ZWNobmljYWxseSB3cm9uZywgYnV0IFBob2JvcyBkZXByZWNhdGVkIGFsLmluZGV4T2YuLi4gZm9yIHNvbWUgcmVhc29uLgoJCWF1dG8gaWR4ID0gaW5kZXhPZihjYXN0KHN0cmluZykgaW5wdXREYXRhLmZyb250KCksICJcclxuXHJcbiIpOwoJCXdoaWxlKGlkeCA9PSAtMSkgewoJCQlpbnB1dERhdGEucG9wRnJvbnQoMCk7CgkJCWlkeCA9IGluZGV4T2YoY2FzdChzdHJpbmcpIGlucHV0RGF0YS5mcm9udCgpLCAiXHJcblxyXG4iKTsKCQl9CgoJCWFzc2VydChpZHggIT0gLTEpOwoKCgkJc3RyaW5nIGNvbnRlbnRUeXBlID0gIiI7CgkJc3RyaW5nW3N0cmluZ10gcmVxdWVzdEhlYWRlcnNIZXJlOwoKCQlzaXplX3QgY29udGVudExlbmd0aDsKCgkJYm9vbCBpc0NodW5rZWQ7CgoJCXsKCQkJaW1wb3J0IGNvcmUucnVudGltZTsKCQkJc2NyaXB0RmlsZU5hbWUgPSBSdW50aW1lLmFyZ3NbMF07CgkJfQoKCgkJaW50IGhlYWRlck51bWJlciA9IDA7CgkJZm9yZWFjaChsaW5lOyBhbC5zcGxpdHRlcihpbnB1dERhdGEuZnJvbnQoKVswIC4uIGlkeF0sICJcclxuIikpCgkJaWYobGluZS5sZW5ndGgpIHsKCQkJaGVhZGVyTnVtYmVyKys7CgkJCWF1dG8gaGVhZGVyID0gY2FzdChzdHJpbmcpIGxpbmUuaWR1cDsKCQkJaWYoaGVhZGVyTnVtYmVyID09IDEpIHsKCQkJCS8vIHJlcXVlc3QgbGluZQoJCQkJYXV0byBwYXJ0cyA9IGFsLnNwbGl0dGVyKGhlYWRlciwgIiAiKTsKCQkJCXJlcXVlc3RNZXRob2QgPSB0byFSZXF1ZXN0TWV0aG9kKHBhcnRzLmZyb250KTsKCQkJCXBhcnRzLnBvcEZyb250KCk7CgkJCQlyZXF1ZXN0VXJpID0gcGFydHMuZnJvbnQ7CgoJCQkJc2NyaXB0TmFtZSA9IHJlcXVlc3RVcmlbMCAuLiBwYXRoSW5mb1N0YXJ0c107CgoJCQkJYXV0byBxdWVzdGlvbiA9IHJlcXVlc3RVcmkuaW5kZXhPZigiPyIpOwoJCQkJaWYocXVlc3Rpb24gPT0gLTEpIHsKCQkJCQlxdWVyeVN0cmluZyA9ICIiOwoJCQkJCS8vIEZJWE1FOiBkb3VibGUgY2hlY2ssIHRoaXMgbWlnaHQgYmUgd3Jvbmcgc2luY2UgaXQgY291bGQgYmUgdXJsIGVuY29kZWQKCQkJCQlwYXRoSW5mbyA9IHJlcXVlc3RVcmlbcGF0aEluZm9TdGFydHMuLiRdOwoJCQkJfSBlbHNlIHsKCQkJCQlxdWVyeVN0cmluZyA9IHJlcXVlc3RVcmlbcXVlc3Rpb24rMS4uJF07CgkJCQkJcGF0aEluZm8gPSByZXF1ZXN0VXJpW3BhdGhJbmZvU3RhcnRzLi5xdWVzdGlvbl07CgkJCQl9CgoJCQkJZ2V0ID0gY2FzdChzdHJpbmdbc3RyaW5nXSkgZ2V0R2V0VmFyaWFibGVzKHF1ZXJ5U3RyaW5nKTsKCQkJCWF1dG8gdWdoID0gZGVjb2RlVmFyaWFibGVzKHF1ZXJ5U3RyaW5nKTsKCQkJCWdldEFycmF5ID0gY2FzdChzdHJpbmdbXVtzdHJpbmddKSBhc3N1bWVVbmlxdWUodWdoKTsKCgkJCQlpZihoZWFkZXIuaW5kZXhPZigiSFRUUC8xLjAiKSAhPSAtMSkgewoJCQkJCWh0dHAxMCA9IHRydWU7CgkJCQkJYXV0b0J1ZmZlciA9IHRydWU7CgkJCQkJaWYoY2xvc2VDb25uZWN0aW9uKQoJCQkJCQkqY2xvc2VDb25uZWN0aW9uID0gdHJ1ZTsKCQkJCX0KCQkJfSBlbHNlIHsKCQkJCS8vIG90aGVyIGhlYWRlcgoJCQkJYXV0byBjb2xvbiA9IGhlYWRlci5pbmRleE9mKCI6Iik7CgkJCQlpZihjb2xvbiA9PSAtMSkKCQkJCQl0aHJvdyBuZXcgRXhjZXB0aW9uKCJIVFRQIGhlYWRlcnMgc2hvdWxkIGhhdmUgYSBjb2xvbiEiKTsKCQkJCXN0cmluZyBuYW1lID0gaGVhZGVyWzAuLmNvbG9uXS50b0xvd2VyOwoJCQkJc3RyaW5nIHZhbHVlID0gaGVhZGVyW2NvbG9uKzIuLiRdOyAvLyBza2lwIHRoZSBjb2xvbiBhbmQgdGhlIHNwYWNlCgoJCQkJcmVxdWVzdEhlYWRlcnNIZXJlW25hbWVdID0gdmFsdWU7CgoJCQkJc3dpdGNoKG5hbWUpIHsKCQkJCQljYXNlICJhY2NlcHQiOgoJCQkJCQlhY2NlcHQgPSB2YWx1ZTsKCQkJCQlicmVhazsKCQkJCQljYXNlICJvcmlnaW4iOgoJCQkJCQlvcmlnaW4gPSB2YWx1ZTsKCQkJCQlicmVhazsKCQkJCQljYXNlICJjb25uZWN0aW9uIjoKCQkJCQkJaWYodmFsdWUgPT0gImNsb3NlIiAmJiBjbG9zZUNvbm5lY3Rpb24pCgkJCQkJCQkqY2xvc2VDb25uZWN0aW9uID0gdHJ1ZTsKCQkJCQkJaWYodmFsdWUudG9Mb3dlcigpLmluZGV4T2YoImtlZXAtYWxpdmUiKSAhPSAtMSkKCQkJCQkJCWtlZXBBbGl2ZVJlcXVlc3RlZCA9IHRydWU7CgkJCQkJYnJlYWs7CgkJCQkJY2FzZSAidHJhbnNmZXItZW5jb2RpbmciOgoJCQkJCQlpZih2YWx1ZSA9PSAiY2h1bmtlZCIpCgkJCQkJCQlpc0NodW5rZWQgPSB0cnVlOwoJCQkJCWJyZWFrOwoJCQkJCWNhc2UgImxhc3QtZXZlbnQtaWQiOgoJCQkJCQlsYXN0RXZlbnRJZCA9IHZhbHVlOwoJCQkJCWJyZWFrOwoJCQkJCWNhc2UgImF1dGhvcml6YXRpb24iOgoJCQkJCQlhdXRob3JpemF0aW9uID0gdmFsdWU7CgkJCQkJYnJlYWs7CgkJCQkJY2FzZSAiY29udGVudC10eXBlIjoKCQkJCQkJY29udGVudFR5cGUgPSB2YWx1ZTsKCQkJCQlicmVhazsKCQkJCQljYXNlICJjb250ZW50LWxlbmd0aCI6CgkJCQkJCWNvbnRlbnRMZW5ndGggPSB0byFzaXplX3QodmFsdWUpOwoJCQkJCWJyZWFrOwoJCQkJCWNhc2UgIngtZm9yd2FyZGVkLWZvciI6CgkJCQkJCXJlbW90ZUFkZHJlc3MgPSB2YWx1ZTsKCQkJCQlicmVhazsKCQkJCQljYXNlICJ4LWZvcndhcmRlZC1ob3N0IjoKCQkJCQljYXNlICJob3N0IjoKCQkJCQkJaG9zdCA9IHZhbHVlOwoJCQkJCWJyZWFrOwoJCQkJCWNhc2UgImFjY2VwdC1lbmNvZGluZyI6CgkJCQkJCWlmKHZhbHVlLmluZGV4T2YoImd6aXAiKSAhPSAtMSkKCQkJCQkJCWFjY2VwdHNHemlwID0gdHJ1ZTsKCQkJCQlicmVhazsKCQkJCQljYXNlICJ1c2VyLWFnZW50IjoKCQkJCQkJdXNlckFnZW50ID0gdmFsdWU7CgkJCQkJYnJlYWs7CgkJCQkJY2FzZSAicmVmZXJlciI6CgkJCQkJCXJlZmVycmVyID0gdmFsdWU7CgkJCQkJYnJlYWs7CgkJCQkJY2FzZSAiY29va2llIjoKCQkJCQkJY29va2llIH49IHZhbHVlOwoJCQkJCWJyZWFrOwoJCQkJCWRlZmF1bHQ6CgkJCQkJCS8vIGlnbm9yZSBpdAoJCQkJfQoKCQkJfQoJCX0KCgkJaW5wdXREYXRhLmNvbnN1bWUoaWR4ICsgNCk7CgkJLy8gZG9uZQoKCQlyZXF1ZXN0SGVhZGVycyA9IGFzc3VtZVVuaXF1ZShyZXF1ZXN0SGVhZGVyc0hlcmUpOwoKCQlCeUNodW5rUmFuZ2UgZGF0YUJ5Q2h1bms7CgoJCS8vIHJlYWRpbmcgQ29udGVudC1MZW5ndGggdHlwZSBkYXRhCgkJLy8gV2UgbmVlZCB0byByZWFkIHVwIHRoZSBkYXRhIHdlIGhhdmUsIGFuZCB3cml0ZSBpdCBvdXQgYXMgYSBjaHVuay4KCQlpZighaXNDaHVua2VkKSB7CgkJCWRhdGFCeUNodW5rID0gYnlDaHVuayhpbnB1dERhdGEsIGNvbnRlbnRMZW5ndGgpOwoJCX0gZWxzZSB7CgkJCS8vIGNodW5rZWQgcmVxdWVzdHMgaGFwcGVuLCBidXQgbm90IGV2ZXJ5IGRheS4gU2luY2Ugd2UgbmVlZCB0byBrbm93CgkJCS8vIHRoZSBjb250ZW50IGxlbmd0aCAoZm9yIG5vdywgbWF5YmUgdGhhdCBzaG91bGQgY2hhbmdlKSwgd2UnbGwgYnVmZmVyCgkJCS8vIHRoZSB3aG9sZSB0aGluZyBoZXJlIGluc3RlYWQgb2YgcGFyc2Ugc3RyZWFtaW5nLiAoSSB0aGluayB0aGlzIGlzIHdoYXQgQXBhY2hlIGRvZXMgYW55d2F5IGluIGNnaSBtb2RlcykKCQkJYXV0byBkYXRhID0gZGVjaHVuayhpbnB1dERhdGEpOwoKCQkJLy8gc2V0IHRoZSByYW5nZSBoZXJlCgkJCWRhdGFCeUNodW5rID0gYnlDaHVuayhkYXRhKTsKCQkJY29udGVudExlbmd0aCA9IGRhdGEubGVuZ3RoOwoJCX0KCgkJYXNzZXJ0KGRhdGFCeUNodW5rICFpcyBudWxsKTsKCgkJaWYoY29udGVudExlbmd0aCkgewoJCQlwcmVwYXJlRm9ySW5jb21pbmdEYXRhQ2h1bmtzKGNvbnRlbnRUeXBlLCBjb250ZW50TGVuZ3RoKTsKCQkJZm9yZWFjaChkYXRhQ2h1bms7IGRhdGFCeUNodW5rKSB7CgkJCQloYW5kbGVJbmNvbWluZ0RhdGFDaHVuayhkYXRhQ2h1bmspOwoJCQl9CgkJCXBvc3RBcnJheSA9IGFzc3VtZVVuaXF1ZShwcHMuX3Bvc3QpOwoJCQlmaWxlc0FycmF5ID0gYXNzdW1lVW5pcXVlKHBwcy5fZmlsZXMpOwoJCQlmaWxlcyA9IGtlZXBMYXN0T2YoZmlsZXNBcnJheSk7CgkJCXBvc3QgPSBrZWVwTGFzdE9mKHBvc3RBcnJheSk7CgkJCXBvc3RKc29uID0gcHBzLnBvc3RKc29uOwoJCQljbGVhblVwUG9zdERhdGFTdGF0ZSgpOwoJCX0KCgkJdGhpcy5wb3J0ID0gcG9ydDsKCQl0aGlzLnJlZmVycmVyID0gcmVmZXJyZXI7CgkJdGhpcy5yZW1vdGVBZGRyZXNzID0gcmVtb3RlQWRkcmVzczsKCQl0aGlzLnVzZXJBZ2VudCA9IHVzZXJBZ2VudDsKCQl0aGlzLmF1dGhvcml6YXRpb24gPSBhdXRob3JpemF0aW9uOwoJCXRoaXMub3JpZ2luID0gb3JpZ2luOwoJCXRoaXMuYWNjZXB0ID0gYWNjZXB0OwoJCXRoaXMubGFzdEV2ZW50SWQgPSBsYXN0RXZlbnRJZDsKCQl0aGlzLmh0dHBzID0gaHR0cHM7CgkJdGhpcy5ob3N0ID0gaG9zdDsKCQl0aGlzLnJlcXVlc3RNZXRob2QgPSByZXF1ZXN0TWV0aG9kOwoJCXRoaXMucmVxdWVzdFVyaSA9IHJlcXVlc3RVcmk7CgkJdGhpcy5wYXRoSW5mbyA9IHBhdGhJbmZvOwoJCXRoaXMucXVlcnlTdHJpbmcgPSBxdWVyeVN0cmluZzsKCgkJdGhpcy5zY3JpcHROYW1lID0gc2NyaXB0TmFtZTsKCQl0aGlzLmdldCA9IGNhc3QoaW1tdXRhYmxlKSBnZXQ7CgkJdGhpcy5nZXRBcnJheSA9IGNhc3QoaW1tdXRhYmxlKSBnZXRBcnJheTsKCQl0aGlzLmtlZXBBbGl2ZVJlcXVlc3RlZCA9IGtlZXBBbGl2ZVJlcXVlc3RlZDsKCQl0aGlzLmFjY2VwdHNHemlwID0gYWNjZXB0c0d6aXA7CgkJdGhpcy5jb29raWUgPSBjb29raWU7CgoJCWNvb2tpZXNBcnJheSA9IGdldENvb2tpZUFycmF5KCk7CgkJY29va2llcyA9IGtlZXBMYXN0T2YoY29va2llc0FycmF5KTsKCgl9CglCdWZmZXJlZElucHV0UmFuZ2UgaWRsb2w7CgoJcHJpdmF0ZSBpbW11dGFibGUoc3RyaW5nW3N0cmluZ10pIGtlZXBMYXN0T2YoaW4gc3RyaW5nW11bc3RyaW5nXSBhcnIpIHsKCQlzdHJpbmdbc3RyaW5nXSBjYTsKCQlmb3JlYWNoKGssIHY7IGFycikKCQkJY2Fba10gPSB2WyQtMV07CgoJCXJldHVybiBhc3N1bWVVbmlxdWUoY2EpOwoJfQoKCS8vIEZJWE1FIGR1cGxpY2F0aW9uCglwcml2YXRlIGltbXV0YWJsZShVcGxvYWRlZEZpbGVbc3RyaW5nXSkga2VlcExhc3RPZihpbiBVcGxvYWRlZEZpbGVbXVtzdHJpbmddIGFycikgewoJCVVwbG9hZGVkRmlsZVtzdHJpbmddIGNhOwoJCWZvcmVhY2goaywgdjsgYXJyKQoJCQljYVtrXSA9IHZbJC0xXTsKCgkJcmV0dXJuIGFzc3VtZVVuaXF1ZShjYSk7Cgl9CgoKCXByaXZhdGUgaW1tdXRhYmxlKHN0cmluZ1tdW3N0cmluZ10pIGdldENvb2tpZUFycmF5KCkgewoJCWF1dG8gZm9yVGhlTG92ZU9mR29kID0gZGVjb2RlVmFyaWFibGVzKGNvb2tpZSwgIjsgIik7CgkJcmV0dXJuIGFzc3VtZVVuaXF1ZShmb3JUaGVMb3ZlT2ZHb2QpOwoJfQoKCS8vIHRoaXMgZnVuY3Rpb24gb25seSBleGlzdHMgYmVjYXVzZSBvZiB0aGUgd2l0aF9jZ2lfcGFja2VkIHRoaW5nLCB3aGljaCBpcwoJLy8gYSBmaWx0aHkgaGFjayBJIHB1dCBpbiBoZXJlIGZvciBhIHdvcmsgYXBwLiBXaGljaCBzdGlsbCBkZXBlbmRzIG9uIGl0LCBzbyBpdAoJLy8gc3RheXMgZm9yIG5vdy4gQnV0IEkgd2FudCB0byByZW1vdmUgaXQuCglwcml2YXRlIGltbXV0YWJsZShzdHJpbmdbc3RyaW5nXSkgZ2V0R2V0VmFyaWFibGVzKGluIHN0cmluZyBxdWVyeVN0cmluZykgewoJCWlmKHF1ZXJ5U3RyaW5nLmxlbmd0aCkgewoJCQlhdXRvIF9nZXQgPSBkZWNvZGVWYXJpYWJsZXNTaW5nbGUocXVlcnlTdHJpbmcpOwoKCQkJLy8gU29tZSBzaXRlcyBhcmUgc2hpdCBhbmQgZG9uJ3QgbGV0IHlvdSBoYW5kbGUgbXVsdGlwbGUgcGFyYW1ldGVycy4KCQkJLy8gSWYgc28sIGNvbXBpbGUgdGhpcyBpbiBhbmQgZW5jb2RlIGl0IGFzIGEgc2luZ2xlIHBhcmFtZXRlcgoJCQl2ZXJzaW9uKHdpdGhfY2dpX3BhY2tlZCkgewoJCQkJYXV0byBpZHggPSBwYXRoSW5mby5pbmRleE9mKCJQQUNLRUQiKTsKCQkJCWlmKGlkeCAhPSAtMSkgewoJCQkJCWF1dG8gcGkgPSBwYXRoSW5mb1tpZHggKyAiUEFDS0VEIi5sZW5ndGggLi4gJF07CgoJCQkJCWF1dG8gX3VucGFja2VkID0gZGVjb2RlVmFyaWFibGVzKAoJCQkJCQljYXN0KHN0cmluZykgYmFzZTY0VXJsRGVjb2RlKHBpKSk7CgoJCQkJCWZvcmVhY2goaywgdjsgX3VucGFja2VkKQoJCQkJCQlfZ2V0W2tdID0gdlskLTFdOwoJCQkJCS8vIHBvc3NpYmxlIHByb2JsZW06IGl0IHVzZWQgdG8gY3V0IFBBQ0tFRCBvZmYgdGhlIHBhdGggaW5mbwoJCQkJCS8vIGJ1dCBpdCBkb2Vzbid0IG5vdy4gSSB3YW50IHRvIGtpbGwgdGhpcyBjcmFwIGFueXdheSB0aG91Z2guCgkJCQl9CgoJCQkJaWYoImFyc2RfcGFja2VkX2RhdGEiIGluIGdldEFycmF5KSB7CgkJCQkJYXV0byBfdW5wYWNrZWQgPSBkZWNvZGVWYXJpYWJsZXMoCgkJCQkJCWNhc3Qoc3RyaW5nKSBiYXNlNjRVcmxEZWNvZGUoZ2V0QXJyYXlbImFyc2RfcGFja2VkX2RhdGEiXVswXSkpOwoKCQkJCQlmb3JlYWNoKGssIHY7IF91bnBhY2tlZCkKCQkJCQkJX2dldFtrXSA9IHZbJC0xXTsKCQkJCX0KCQkJfQoKCQkJcmV0dXJuIGFzc3VtZVVuaXF1ZShfZ2V0KTsKCQl9CgoJCXJldHVybiBudWxsOwoJfQoJLy8vIFZlcnkgc2ltcGxlIG1ldGhvZCB0byByZXF1aXJlIGEgYmFzaWMgYXV0aCB1c2VybmFtZSBhbmQgcGFzc3dvcmQuCgkvLy8gSWYgdGhlIGh0dHAgcmVxdWVzdCBkb2Vzbid0IGluY2x1ZGUgdGhlIHJlcXVpcmVkIGNyZWRlbnRpYWxzLCBpdCB0aHJvd3MgYQoJLy8vIEhUVFAgNDAxIGVycm9yLCBhbmQgYW4gZXhjZXB0aW9uLgoJLy8vCgkvLy8gTm90ZTogYmFzaWMgYXV0aCBkb2VzIG5vdCBwcm92aWRlIGdyZWF0IHNlY3VyaXR5LCBlc3BlY2lhbGx5IG92ZXIgdW5lbmNyeXB0ZWQgSFRUUDsKCS8vLyB0aGUgdXNlcidzIGNyZWRlbnRpYWxzIGFyZSBzZW50IGluIHBsYWluIHRleHQgb24gZXZlcnkgcmVxdWVzdC4KCS8vLwoJLy8vIElmIHlvdSBhcmUgdXNpbmcgQXBhY2hlLCB0aGUgSFRUUF9BVVRIT1JJWkFUSU9OIHZhcmlhYmxlIG1heSBub3QgYmUgc2VudCB0byB0aGUKCS8vLyBhcHBsaWNhdGlvbi4gRWl0aGVyIHVzZSBBcGFjaGUncyBidWlsdCBpbiBtZXRob2RzIGZvciBiYXNpYyBhdXRoZW50aWNhdGlvbiwgb3IgYWRkCgkvLy8gc29tZXRoaW5nIGFsb25nIHRoZXNlIGxpbmVzIHRvIHlvdXIgc2VydmVyIGNvbmZpZ3VyYXRpb246CgkvLy8KCS8vLyAgICAgIFJld3JpdGVFbmdpbmUgT24gCgkvLy8gICAgICBSZXdyaXRlQ29uZCAle0hUVFA6QXV0aG9yaXphdGlvbn0gXiguKikgCgkvLy8gICAgICBSZXdyaXRlUnVsZSBeKC4qKSAtIFtFPUhUVFBfQVVUSE9SSVpBVElPTjolMV0KCS8vLwoJLy8vIFRvIGVuc3VyZSB0aGUgbmVjZXNzYXJ5IGRhdGEgaXMgYXZhaWxhYmxlIHRvIGNnaS5kLgoJdm9pZCByZXF1aXJlQmFzaWNBdXRoKHN0cmluZyB1c2VyLCBzdHJpbmcgcGFzcywgc3RyaW5nIG1lc3NhZ2UgPSBudWxsKSB7CgkJaWYoYXV0aG9yaXphdGlvbiAhPSAiQmFzaWMgIiB+IEJhc2U2NC5lbmNvZGUoY2FzdChpbW11dGFibGUodWJ5dGUpW10pICh1c2VyIH4gIjoiIH4gcGFzcykpKSB7CgkJCXNldFJlc3BvbnNlU3RhdHVzKCI0MDEgQXV0aG9yaXphdGlvbiBSZXF1aXJlZCIpOwoJCQloZWFkZXIgKCJXV1ctQXV0aGVudGljYXRlOiBCYXNpYyByZWFsbT1cIiJ+bWVzc2FnZX4iXCIiKTsKCQkJY2xvc2UoKTsKCQkJdGhyb3cgbmV3IEV4Y2VwdGlvbigiTm90IGF1dGhvcml6ZWQ7IGdvdCAiIH4gYXV0aG9yaXphdGlvbik7CgkJfQoJfQoKCS8vLyBWZXJ5IHNpbXBsZSBjYWNoaW5nIGNvbnRyb2xzIC0gc2V0Q2FjaGUoZmFsc2UpIG1lYW5zIGl0IHdpbGwgbmV2ZXIgYmUgY2FjaGVkLiBHb29kIGZvciByYXBpZGx5IHVwZGF0ZWQgb3Igc2Vuc2l0aXZlIHNpdGVzLgoJLy8vIHNldENhY2hlKHRydWUpIG1lYW5zIGl0IHdpbGwgYWx3YXlzIGJlIGNhY2hlZCBmb3IgYXMgbG9uZyBhcyBwb3NzaWJsZS4gQmVzdCBmb3Igc3RhdGljIGNvbnRlbnQuCgkvLy8gVXNlIHNldFJlc3BvbnNlRXhwaXJlcyBhbmQgdXBkYXRlUmVzcG9uc2VFeHBpcmVzIGZvciBtb3JlIGNvbnRyb2wKCXZvaWQgc2V0Q2FjaGUoYm9vbCBhbGxvd0NhY2hpbmcpIHsKCQlub0NhY2hlID0gIWFsbG93Q2FjaGluZzsKCX0KCgkvLy8gU2V0IHRvIHRydWUgYW5kIHVzZSBjZ2kud3JpdGUoZGF0YSwgdHJ1ZSk7IHRvIHNlbmQgYSBnemlwcGVkIHJlc3BvbnNlIHRvIGJyb3dzZXJzCgkvLy8gd2hvIGNhbiBhY2NlcHQgaXQKCWJvb2wgZ3ppcFJlc3BvbnNlOwoKCWltbXV0YWJsZSBib29sIGFjY2VwdHNHemlwOwoJaW1tdXRhYmxlIGJvb2wga2VlcEFsaXZlUmVxdWVzdGVkOwoKCS8vLyBTZXQgdG8gdHJ1ZSBpZiBhbmQgb25seSBpZiB0aGlzIHdhcyBpbml0aWFsaXplZCB3aXRoIGNvbW1hbmQgbGluZSBhcmd1bWVudHMKCWltbXV0YWJsZSBib29sIGlzQ2FsbGVkV2l0aENvbW1hbmRMaW5lQXJndW1lbnRzOwoKCS8vLyBUaGlzIGdldHMgYSBmdWxsIHVybCBmb3IgdGhlIGN1cnJlbnQgcmVxdWVzdCwgaW5jbHVkaW5nIHBvcnQsIHByb3RvY29sLCBob3N0LCBwYXRoLCBhbmQgcXVlcnkKCXN0cmluZyBnZXRDdXJyZW50Q29tcGxldGVVcmkoKSBjb25zdCB7CgkJdXNob3J0IGRlZmF1bHRQb3J0ID0gaHR0cHMgPyA0NDMgOiA4MDsKCgkJcmV0dXJuIGZvcm1hdCgiaHR0cCVzOi8vJXMlcyVzIiwKCQkJaHR0cHMgPyAicyIgOiAiIiwKCQkJaG9zdCwKCQkJKCFwb3J0IHx8IHBvcnQgPT0gZGVmYXVsdFBvcnQpID8gIiIgOiAiOiIgfiB0byFzdHJpbmcocG9ydCksCgkJCXJlcXVlc3RVcmkpOwoJfQoKCS8vLyBZb3UgY2FuIG92ZXJyaWRlIHRoaXMgaWYgeW91ciBzaXRlIGJhc2UgdXJsIGlzbid0IHRoZSBzYW1lIGFzIHRoZSBzY3JpcHQgbmFtZQoJc3RyaW5nIGxvZ2ljYWxTY3JpcHROYW1lKCkgY29uc3QgewoJCXJldHVybiBzY3JpcHROYW1lOwoJfQoKCS8vLyBTZXRzIHRoZSBIVFRQIHN0YXR1cyBvZiB0aGUgcmVzcG9uc2UuIEZvciBleGFtcGxlLCAiNDA0IEZpbGUgTm90IEZvdW5kIiBvciAiNTAwIEludGVybmFsIFNlcnZlciBFcnJvciIuCgkvLy8gSXQgYXNzdW1lcyAiMjAwIE9LIiwgYW5kIGF1dG9tYXRpY2FsbHkgY2hhbmdlcyB0byAiMzAyIEZvdW5kIiBpZiB5b3UgY2FsbCBzZXRSZXNwb25zZUxvY2F0aW9uKCkuCgkvLy8gTm90ZSBzZXRSZXNwb25zZVN0YXR1cygpIG11c3QgYmUgY2FsbGVkICpiZWZvcmUqIHlvdSB3cml0ZSgpIGFueSBkYXRhIHRvIHRoZSBvdXRwdXQuCgl2b2lkIHNldFJlc3BvbnNlU3RhdHVzKHN0cmluZyBzdGF0dXMpIHsKCQlhc3NlcnQoIW91dHB1dHRlZFJlc3BvbnNlRGF0YSk7CgkJcmVzcG9uc2VTdGF0dXMgPSBzdGF0dXM7Cgl9Cglwcml2YXRlIHN0cmluZyByZXNwb25zZVN0YXR1cyA9IG51bGw7CgoJLy8vIFJldHVybnMgdHJ1ZSBpZiBpdCBpcyBzdGlsbCBwb3NzaWJsZSB0byBvdXRwdXQgaGVhZGVycwoJYm9vbCBjYW5PdXRwdXRIZWFkZXJzKCkgewoJCXJldHVybiAhaXNDbG9zZWQgJiYgIW91dHB1dHRlZFJlc3BvbnNlRGF0YTsKCX0KCgkvLy8gU2V0cyB0aGUgbG9jYXRpb24gaGVhZGVyLCB3aGljaCB0aGUgYnJvd3NlciB3aWxsIHJlZGlyZWN0IHRoZSB1c2VyIHRvIGF1dG9tYXRpY2FsbHkuCgkvLy8gTm90ZSBzZXRSZXNwb25zZUxvY2F0aW9uKCkgbXVzdCBiZSBjYWxsZWQgKmJlZm9yZSogeW91IHdyaXRlKCkgYW55IGRhdGEgdG8gdGhlIG91dHB1dC4KCS8vLyBUaGUgb3B0aW9uYWwgaW1wb3J0YW50IGFyZ3VtZW50IGlzIHVzZWQgaWYgaXQncyBhIGRlZmF1bHQgc3VnZ2VzdGlvbiByYXRoZXIgdGhhbiBzb21ldGhpbmcgdG8gaW5zaXN0IHVwb24uCgl2b2lkIHNldFJlc3BvbnNlTG9jYXRpb24oc3RyaW5nIHVyaSwgYm9vbCBpbXBvcnRhbnQgPSB0cnVlLCBzdHJpbmcgc3RhdHVzID0gbnVsbCkgewoJCWlmKCFpbXBvcnRhbnQgJiYgaXNDdXJyZW50UmVzcG9uc2VMb2NhdGlvbkltcG9ydGFudCkKCQkJcmV0dXJuOyAvLyBpbXBvcnRhbnQgcmVkaXJlY3RzIGFsd2F5cyBvdmVycmlkZSB1bmltcG9ydGFudCBvbmVzCgoJCWlmKHVyaSBpcyBudWxsKSB7CgkJCXJlc3BvbnNlU3RhdHVzID0gIjIwMCBPSyI7CgkJCXJlc3BvbnNlTG9jYXRpb24gPSBudWxsOwoJCQlpc0N1cnJlbnRSZXNwb25zZUxvY2F0aW9uSW1wb3J0YW50ID0gaW1wb3J0YW50OwoJCQlyZXR1cm47IC8vIHRoaXMganVzdCBjYW5jZWxzIHRoZSByZWRpcmVjdAoJCX0KCgkJYXNzZXJ0KCFvdXRwdXR0ZWRSZXNwb25zZURhdGEpOwoJCWlmKHN0YXR1cyBpcyBudWxsKQoJCQlyZXNwb25zZVN0YXR1cyA9ICIzMDIgRm91bmQiOwoJCWVsc2UKCQkJcmVzcG9uc2VTdGF0dXMgPSBzdGF0dXM7CgoJCXJlc3BvbnNlTG9jYXRpb24gPSB1cmkuc3RyaXA7CgkJaXNDdXJyZW50UmVzcG9uc2VMb2NhdGlvbkltcG9ydGFudCA9IGltcG9ydGFudDsKCX0KCXByb3RlY3RlZCBzdHJpbmcgcmVzcG9uc2VMb2NhdGlvbiA9IG51bGw7Cglwcml2YXRlIGJvb2wgaXNDdXJyZW50UmVzcG9uc2VMb2NhdGlvbkltcG9ydGFudCA9IGZhbHNlOwoKCS8vLyBTZXRzIHRoZSBFeHBpcmVzOiBodHRwIGhlYWRlci4gU2VlIGFsc286IHVwZGF0ZVJlc3BvbnNlRXhwaXJlcywgc2V0UHVibGljQ2FjaGluZwoJLy8vIFRoZSBwYXJhbWV0ZXIgaXMgaW4gdW5peF90aW1lc3RhbXAgKiAxMDAwLiBUcnkgc2V0UmVzcG9uc2VFeHBpcmVzKGdldFVUQ3RpbWUoKSArIFNPTUUgQU1PVU5UKSBmb3Igbm9ybWFsIHVzZS4KCS8vLyBOb3RlOiB0aGUgd2hlbiBwYXJhbWV0ZXIgaXMgZGlmZmVyZW50IHRoYW4gc2V0Q29va2llJ3MgZXhwaXJlIHBhcmFtZXRlci4KCXZvaWQgc2V0UmVzcG9uc2VFeHBpcmVzKGxvbmcgd2hlbiwgYm9vbCBpc1B1YmxpYyA9IGZhbHNlKSB7CgkJcmVzcG9uc2VFeHBpcmVzID0gd2hlbjsKCQlzZXRDYWNoZSh0cnVlKTsgLy8gbmVlZCB0byBlbmFibGUgY2FjaGluZyBzbyB0aGUgZGF0ZSBoYXMgbWVhbmluZwoKCQlyZXNwb25zZUlzUHVibGljID0gaXNQdWJsaWM7Cgl9Cglwcml2YXRlIGxvbmcgcmVzcG9uc2VFeHBpcmVzID0gbG9uZy5taW47Cglwcml2YXRlIGJvb2wgcmVzcG9uc2VJc1B1YmxpYyA9IGZhbHNlOwoKCS8vLyBUaGlzIGlzIGxpa2Ugc2V0UmVzcG9uc2VFeHBpcmVzLCBidXQgaXQgY2FuIGJlIGNhbGxlZCBtdWx0aXBsZSB0aW1lcy4gVGhlIHNldHRpbmcgbW9zdCBpbiB0aGUgcGFzdCBpcyB0aGUgb25lIGtlcHQuCgkvLy8gSWYgeW91IGhhdmUgbXVsdGlwbGUgZnVuY3Rpb25zLCB0aGV5IGFsbCBtaWdodCBjYWxsIHVwZGF0ZVJlc3BvbnNlRXhwaXJlcyBhYm91dCB0aGVpciBvd24gcmV0dXJuIHZhbHVlLiBUaGUgcHJvZ3JhbQoJLy8vIG91dHB1dCBhcyBhIHdob2xlIGlzIGFzIGNhY2hlYWJsZSBhcyB0aGUgbGVhc3QgY2FjaGFibGUgcGFydCBpbiB0aGUgY2hhaW4uCgoJLy8vIHNldENhY2hlKGZhbHNlKSBhbHdheXMgb3ZlcnJpZGVzIHRoaXMgLSBpdCBpcywgYnkgZGVmaW5pdGlvbiwgdGhlIHN0cmljdGVzdCBhbnRpLWNhY2hlIHN0YXRlbWVudCBhdmFpbGFibGUuIElmIHlvdXIgc2l0ZSBvdXRwdXRzIHNlbnNpdGl2ZSB1c2VyIGRhdGEsIHlvdSBzaG91bGQgcHJvYmFibHkgY2FsbCBzZXRDYWNoZShmYWxzZSkgd2hlbiB5b3UgZG8sIHRvIGVuc3VyZSBubyBvdGhlciBmdW5jdGlvbnMgd2lsbCBjYWNoZSB0aGUgY29udGVudCwgYXMgaXQgbWF5IGJlIGEgcHJpdmFjeSByaXNrLgoJLy8vIENvbnZlcnNlbHksIHNldHRpbmcgaGVyZSBvdmVycmlkZXMgc2V0Q2FjaGUodHJ1ZSksIHNpbmNlIGFueSBleHBpcmF0aW9uIGRhdGUgaXMgaW4gdGhlIHBhc3Qgb2YgaW5maW5pdHkuCgl2b2lkIHVwZGF0ZVJlc3BvbnNlRXhwaXJlcyhsb25nIHdoZW4sIGJvb2wgaXNQdWJsaWMpIHsKCQlpZihyZXNwb25zZUV4cGlyZXMgPT0gbG9uZy5taW4pCgkJCXNldFJlc3BvbnNlRXhwaXJlcyh3aGVuLCBpc1B1YmxpYyk7CgkJZWxzZSBpZih3aGVuIDwgcmVzcG9uc2VFeHBpcmVzKQoJCQlzZXRSZXNwb25zZUV4cGlyZXMod2hlbiwgcmVzcG9uc2VJc1B1YmxpYyAmJiBpc1B1YmxpYyk7IC8vIGlmIGFueSBwYXJ0IG9mIGl0IGlzIHByaXZhdGUsIGl0IGFsbCBpcwoJfQoKCS8qCgkvLy8gU2V0IHRvIHRydWUgaWYgeW91IHdhbnQgdGhlIHJlc3VsdCB0byBiZSBjYWNoZWQgcHVibGljYWxseSAtIHRoYXQgaXMsIGlzIHRoZSBjb250ZW50IHNoYXJlZD8KCS8vLyBTaG91bGQgZ2VuZXJhbGx5IGJlIGZhbHNlIGlmIHRoZSB1c2VyIGlzIGxvZ2dlZCBpbi4gSXQgYXNzdW1lcyBwcml2YXRlIGNhY2hlIG9ubHkuCgkvLy8gc2V0Q2FjaGUodHJ1ZSkgYWxzbyB0dXJucyBvbiBwdWJsaWMgY2FjaGluZywgYW5kIHNldENhY2hlKGZhbHNlKSBzZXRzIHRvIHByaXZhdGUuCgl2b2lkIHNldFB1YmxpY0NhY2hpbmcoYm9vbCBhbGxvd1B1YmxpY0NhY2hlcykgewoJCXB1YmxpY0NhY2hpbmcgPSBhbGxvd1B1YmxpY0NhY2hlczsKCX0KCXByaXZhdGUgYm9vbCBwdWJsaWNDYWNoaW5nID0gZmFsc2U7CgkqLwoKCS8vLyBTZXRzIGFuIEhUVFAgY29va2llLCBhdXRvbWF0aWNhbGx5IGVuY29kaW5nIHRoZSBkYXRhIHRvIHRoZSBjb3JyZWN0IHN0cmluZy4KCS8vLyBleHBpcmVzSW4gaXMgaG93IG1hbnkgbWlsbGlzZWNvbmRzIGluIHRoZSBmdXR1cmUgdGhlIGNvb2tpZSB3aWxsIGV4cGlyZS4KCS8vLyBUSVA6IHRvIG1ha2UgYSBjb29raWUgYWNjZXNzaWJsZSBmcm9tIHN1YmRvbWFpbnMsIHNldCB0aGUgZG9tYWluIHRvIC55b3VyZG9tYWluLmNvbS4KCS8vLyBOb3RlIHNldENvb2tpZSgpIG11c3QgYmUgY2FsbGVkICpiZWZvcmUqIHlvdSB3cml0ZSgpIGFueSBkYXRhIHRvIHRoZSBvdXRwdXQuCgl2b2lkIHNldENvb2tpZShzdHJpbmcgbmFtZSwgc3RyaW5nIGRhdGEsIGxvbmcgZXhwaXJlc0luID0gMCwgc3RyaW5nIHBhdGggPSBudWxsLCBzdHJpbmcgZG9tYWluID0gbnVsbCwgYm9vbCBodHRwT25seSA9IGZhbHNlLCBib29sIHNlY3VyZSA9IGZhbHNlKSB7CgkJYXNzZXJ0KCFvdXRwdXR0ZWRSZXNwb25zZURhdGEpOwoJCXN0cmluZyBjb29raWUgPSBzdGQudXJpLmVuY29kZUNvbXBvbmVudChuYW1lKSB+ICI9IjsKCQljb29raWUgfj0gc3RkLnVyaS5lbmNvZGVDb21wb25lbnQoZGF0YSk7CgkJaWYocGF0aCAhaXMgbnVsbCkKCQkJY29va2llIH49ICI7IHBhdGg9IiB+IHBhdGg7CgkJLy8gRklYTUU6IHNob3VsZCBJIGp1c3QgYmUgdXNpbmcgbWF4LWFnZSBoZXJlPyAoYWxzbyBpbiBjYWNoZSBiZWxvdykKCQlpZihleHBpcmVzSW4gIT0gMCkKCQkJY29va2llIH49ICI7IGV4cGlyZXM9IiB+IHByaW50RGF0ZShjYXN0KERhdGVUaW1lKSBDbG9jay5jdXJyVGltZShVVEMoKSkgKyBkdXIhIm1zZWNzIihleHBpcmVzSW4pKTsKCQlpZihkb21haW4gIWlzIG51bGwpCgkJCWNvb2tpZSB+PSAiOyBkb21haW49IiB+IGRvbWFpbjsKCQlpZihzZWN1cmUgPT0gdHJ1ZSkKCQkJY29va2llIH49ICI7IFNlY3VyZSI7CgkJaWYoaHR0cE9ubHkgPT0gdHJ1ZSApCgkJCWNvb2tpZSB+PSAiOyBIdHRwT25seSI7CgoJCWlmKGF1dG8gaWR4ID0gbmFtZSBpbiBjb29raWVJbmRleGVzKSB7CgkJCXJlc3BvbnNlQ29va2llc1sqaWR4XSA9IGNvb2tpZTsKCQl9IGVsc2UgewoJCQljb29raWVJbmRleGVzW25hbWVdID0gcmVzcG9uc2VDb29raWVzLmxlbmd0aDsKCQkJcmVzcG9uc2VDb29raWVzIH49IGNvb2tpZTsKCQl9Cgl9Cglwcml2YXRlIHN0cmluZ1tdIHJlc3BvbnNlQ29va2llczsKCXByaXZhdGUgc2l6ZV90W3N0cmluZ10gY29va2llSW5kZXhlczsKCgkvLy8gQ2xlYXJzIGEgcHJldmlvdXNseSBzZXQgY29va2llIHdpdGggdGhlIGdpdmVuIG5hbWUsIHBhdGgsIGFuZCBkb21haW4uCgl2b2lkIGNsZWFyQ29va2llKHN0cmluZyBuYW1lLCBzdHJpbmcgcGF0aCA9IG51bGwsIHN0cmluZyBkb21haW4gPSBudWxsKSB7CgkJYXNzZXJ0KCFvdXRwdXR0ZWRSZXNwb25zZURhdGEpOwoJCXNldENvb2tpZShuYW1lLCAiIiwgMSwgcGF0aCwgZG9tYWluKTsKCX0KCgkvLy8gU2V0cyB0aGUgY29udGVudCB0eXBlIG9mIHRoZSByZXNwb25zZSwgZm9yIGV4YW1wbGUgInRleHQvaHRtbCIgKHRoZSBkZWZhdWx0KSBmb3IgSFRNTCwgb3IgImltYWdlL3BuZyIgZm9yIGEgUE5HIGltYWdlCgl2b2lkIHNldFJlc3BvbnNlQ29udGVudFR5cGUoc3RyaW5nIGN0KSB7CgkJYXNzZXJ0KCFvdXRwdXR0ZWRSZXNwb25zZURhdGEpOwoJCXJlc3BvbnNlQ29udGVudFR5cGUgPSBjdDsKCX0KCXByaXZhdGUgc3RyaW5nIHJlc3BvbnNlQ29udGVudFR5cGUgPSBudWxsOwoKCS8vLyBBZGRzIGEgY3VzdG9tIGhlYWRlci4gSXQgc2hvdWxkIGJlIHRoZSBuYW1lOiB2YWx1ZSwgYnV0IHdpdGhvdXQgYW55IGxpbmUgdGVybWluYXRvci4KCS8vLyBGb3IgZXhhbXBsZTogaGVhZGVyKCJYLU15LUhlYWRlcjogU29tZSB2YWx1ZSIpOwoJLy8vIE5vdGUgeW91IHNob3VsZCB1c2UgdGhlIHNwZWNpYWxpemVkIGZ1bmN0aW9ucyBpbiB0aGlzIG9iamVjdCBpZiBwb3NzaWJsZSB0byBhdm9pZAoJLy8vIGR1cGxpY2F0ZXMgaW4gdGhlIG91dHB1dC4KCXZvaWQgaGVhZGVyKHN0cmluZyBoKSB7CgkJY3VzdG9tSGVhZGVycyB+PSBoOwoJfQoKCXByaXZhdGUgc3RyaW5nW10gY3VzdG9tSGVhZGVyczsKCXByaXZhdGUgYm9vbCB3ZWJzb2NrZXRNb2RlOwoKCXZvaWQgZmx1c2hIZWFkZXJzKGNvbnN0KHZvaWQpW10gdCwgYm9vbCBpc0FsbCA9IGZhbHNlKSB7CgkJc3RyaW5nW10gaGQ7CgkJLy8gRmx1c2ggdGhlIGhlYWRlcnMKCQlpZihyZXNwb25zZVN0YXR1cyAhaXMgbnVsbCkgewoJCQlpZihucGgpIHsKCQkJCWlmKGh0dHAxMCkKCQkJCQloZCB+PSAiSFRUUC8xLjAgIiB+IHJlc3BvbnNlU3RhdHVzOwoJCQkJZWxzZQoJCQkJCWhkIH49ICJIVFRQLzEuMSAiIH4gcmVzcG9uc2VTdGF0dXM7CgkJCX0gZWxzZQoJCQkJaGQgfj0gIlN0YXR1czogIiB+IHJlc3BvbnNlU3RhdHVzOwoJCX0gZWxzZSBpZiAobnBoKSB7CgkJCWlmKGh0dHAxMCkKCQkJCWhkIH49ICJIVFRQLzEuMCAyMDAgT0siOwoJCQllbHNlCgkJCQloZCB+PSAiSFRUUC8xLjEgMjAwIE9LIjsKCQl9CgoJCWlmKHdlYnNvY2tldE1vZGUpCgkJCWdvdG8gd2Vic29ja2V0OwoKCQlpZihucGgpIHsgLy8gd2UncmUgcmVzcG9uc2libGUgZm9yIHNldHRpbmcgdGhlIGRhdGUgdG9vIGFjY29yZGluZyB0byBodHRwIDEuMQoJCQloZCB+PSAiRGF0ZTogIiB+IHByaW50RGF0ZShjYXN0KERhdGVUaW1lKSBDbG9jay5jdXJyVGltZShVVEMoKSkpOwoJCX0KCgkJLy8gRklYTUU6IHdoYXQgaWYgdGhlIHVzZXIgd2FudHMgdG8gc2V0IGhpcyBvd24gY29udGVudC1sZW5ndGg/CgkJLy8gVGhlIGN1c3RvbSBoZWFkZXIgZnVuY3Rpb24gY2FuIGRvIGl0LCBzbyBtYXliZSB0aGF0J3MgYmVzdC4KCQkvLyBPciB3ZSBjb3VsZCByZXVzZSB0aGUgaXNBbGwgcGFyYW0uCgkJaWYocmVzcG9uc2VMb2NhdGlvbiAhaXMgbnVsbCkgewoJCQloZCB+PSAiTG9jYXRpb246ICIgfiByZXNwb25zZUxvY2F0aW9uOwoJCX0KCQlpZighbm9DYWNoZSAmJiByZXNwb25zZUV4cGlyZXMgIT0gbG9uZy5taW4pIHsgLy8gYW4gZXhwbGljaXQgZXhwaXJhdGlvbiBkYXRlIGlzIHNldAoJCQlhdXRvIGV4cGlyZXMgPSBTeXNUaW1lKHVuaXhUaW1lVG9TdGRUaW1lKGNhc3QoaW50KShyZXNwb25zZUV4cGlyZXMgLyAxMDAwKSksIFVUQygpKTsKCQkJaGQgfj0gIkV4cGlyZXM6ICIgfiBwcmludERhdGUoCgkJCQljYXN0KERhdGVUaW1lKSBleHBpcmVzKTsKCQkJLy8gRklYTUU6IGFzc3VtaW5nIGV2ZXJ5dGhpbmcgaXMgcHJpdmF0ZSB1bmxlc3MgeW91IHVzZSBub2NhY2hlIC0gZ2VuZXJhbGx5IHJpZ2h0IGZvciBkeW5hbWljIHBhZ2VzLCBidXQgbm90IG5lY2Vzc2FyaWx5CgkJCWhkIH49ICJDYWNoZS1Db250cm9sOiAifihyZXNwb25zZUlzUHVibGljID8gInB1YmxpYyIgOiAicHJpdmF0ZSIpfiIsIG5vLWNhY2hlPVwic2V0LWNvb2tpZSwgc2V0LWNvb2tpZTJcIiI7CgkJfQoJCWlmKHJlc3BvbnNlQ29va2llcyAhaXMgbnVsbCAmJiByZXNwb25zZUNvb2tpZXMubGVuZ3RoID4gMCkgewoJCQlmb3JlYWNoKGM7IHJlc3BvbnNlQ29va2llcykKCQkJCWhkIH49ICJTZXQtQ29va2llOiAiIH4gYzsKCQl9CgkJaWYobm9DYWNoZSkgeyAvLyB3ZSBzcGVjaWZpY2FsbHkgZG8gbm90IHdhbnQgY2FjaGluZyAodGhpcyBpcyBhY3R1YWxseSB0aGUgZGVmYXVsdCkKCQkJaGQgfj0gIkNhY2hlLUNvbnRyb2w6IHByaXZhdGUsIG5vLWNhY2hlPVwic2V0LWNvb2tpZVwiIjsKCQkJaGQgfj0gIkV4cGlyZXM6IDAiOwoJCQloZCB+PSAiUHJhZ21hOiBuby1jYWNoZSI7CgkJfSBlbHNlIHsKCQkJaWYocmVzcG9uc2VFeHBpcmVzID09IGxvbmcubWluKSB7IC8vIGNhY2hpbmcgd2FzIGVuYWJsZWQsIGJ1dCB3aXRob3V0IGEgZGF0ZSBzZXQgLSB0aGF0IG1lYW5zIGFzc3VtZSBjYWNoZSBmb3JldmVyCgkJCQloZCB+PSAiQ2FjaGUtQ29udHJvbDogcHVibGljIjsKCQkJCWhkIH49ICJFeHBpcmVzOiBUdWUsIDMxIERlYyAyMDMwIDE0OjAwOjAwIEdNVCI7IC8vIEZJWE1FOiBzaG91bGQgbm90IGJlIG1vcmUgdGhhbiBvbmUgeWVhciBpbiB0aGUgZnV0dXJlCgkJCX0KCQl9CgkJaWYocmVzcG9uc2VDb250ZW50VHlwZSAhaXMgbnVsbCkgewoJCQloZCB+PSAiQ29udGVudC1UeXBlOiAiIH4gcmVzcG9uc2VDb250ZW50VHlwZTsKCQl9IGVsc2UKCQkJaGQgfj0gIkNvbnRlbnQtVHlwZTogdGV4dC9odG1sOyBjaGFyc2V0PXV0Zi04IjsKCgkJaWYoZ3ppcFJlc3BvbnNlICYmIGFjY2VwdHNHemlwICYmIGlzQWxsKSB7IC8vIEZJWE1FOiBpc0FsbCByZWFsbHkgc2hvdWxkbid0IGJlIG5lY2Vzc2FyeQoJCQloZCB+PSAiQ29udGVudC1FbmNvZGluZzogZ3ppcCI7CgkJfQoKCgkJaWYoIWlzQWxsKSB7CgkJCWlmKG5waCAmJiAhaHR0cDEwKSB7CgkJCQloZCB+PSAiVHJhbnNmZXItRW5jb2Rpbmc6IGNodW5rZWQiOwoJCQkJcmVzcG9uc2VDaHVua2VkID0gdHJ1ZTsKCQkJfQoJCX0gZWxzZSB7CgkJCWhkIH49ICJDb250ZW50LUxlbmd0aDogIiB+IHRvIXN0cmluZyh0Lmxlbmd0aCk7CgkJCWlmKG5waCAmJiBrZWVwQWxpdmVSZXF1ZXN0ZWQpIHsKCQkJCWhkIH49ICJDb25uZWN0aW9uOiBLZWVwLUFsaXZlIjsKCQkJfQoJCX0KCgkJd2Vic29ja2V0OgoJCWlmKGN1c3RvbUhlYWRlcnMgIWlzIG51bGwpCgkJCWhkIH49IGN1c3RvbUhlYWRlcnM7CgoJCS8vIEZJWE1FOiB3aGF0IGFib3V0IGR1cGxpY2F0ZWQgaGVhZGVycz8KCgkJZm9yZWFjaChoOyBoZCkgewoJCQlpZihyYXdEYXRhT3V0cHV0ICFpcyBudWxsKQoJCQkJcmF3RGF0YU91dHB1dChjYXN0KGNvbnN0KHVieXRlKVtdKSAoaCB+ICJcclxuIikpOwoJCQllbHNlCgkJCQl3cml0ZWxuKGgpOwoJCX0KCQlpZihyYXdEYXRhT3V0cHV0ICFpcyBudWxsKQoJCQlyYXdEYXRhT3V0cHV0KGNhc3QoY29uc3QodWJ5dGUpW10pICgiXHJcbiIpKTsKCQllbHNlCgkJCXdyaXRlbG4oIiIpOwoKCQlvdXRwdXR0ZWRSZXNwb25zZURhdGEgPSB0cnVlOwoJfQoKCS8vLyBXcml0ZXMgdGhlIGRhdGEgdG8gdGhlIG91dHB1dCwgZmx1c2hpbmcgaGVhZGVycyBpZiB0aGV5IGhhdmUgbm90IHlldCBiZWVuIHNlbnQuCgl2b2lkIHdyaXRlKGNvbnN0KHZvaWQpW10gdCwgYm9vbCBpc0FsbCA9IGZhbHNlLCBib29sIG1heWJlQXV0b0Nsb3NlID0gdHJ1ZSkgewoJCWFzc2VydCghY2xvc2VkLCAiT3V0cHV0IGhhcyBhbHJlYWR5IGJlZW4gY2xvc2VkIik7CgoJCWlmKGd6aXBSZXNwb25zZSAmJiBhY2NlcHRzR3ppcCAmJiBpc0FsbCkgeyAvLyBGSVhNRTogaXNBbGwgcmVhbGx5IHNob3VsZG4ndCBiZSBuZWNlc3NhcnkKCQkJLy8gYWN0dWFsbHkgZ3ppcCB0aGUgZGF0YSBoZXJlCgoJCQlhdXRvIGMgPSBuZXcgQ29tcHJlc3MoSGVhZGVyRm9ybWF0Lmd6aXApOyAvLyB3YW50IGd6aXAKCgkJCWF1dG8gZGF0YSA9IGMuY29tcHJlc3ModCk7CgkJCWRhdGEgfj0gYy5mbHVzaCgpOwoKCQkJLy8gc3RkLmZpbGUud3JpdGUoIi90bXAvbGFzdC1pdGVtIiwgZGF0YSk7CgoJCQl0ID0gZGF0YTsKCQl9CgoJCWlmKCFvdXRwdXR0ZWRSZXNwb25zZURhdGEgJiYgKCFhdXRvQnVmZmVyIHx8IGlzQWxsKSkgewoJCQlmbHVzaEhlYWRlcnModCwgaXNBbGwpOwoJCX0KCgkJaWYocmVxdWVzdE1ldGhvZCAhPSBSZXF1ZXN0TWV0aG9kLkhFQUQgJiYgdC5sZW5ndGggPiAwKSB7CgkJCWlmIChhdXRvQnVmZmVyKSB7CgkJCQlvdXRwdXRCdWZmZXIgfj0gY2FzdCh1Ynl0ZVtdKSB0OwoJCQl9CgkJCWlmKCFhdXRvQnVmZmVyIHx8IGlzQWxsKSB7CgkJCQlpZihyYXdEYXRhT3V0cHV0ICFpcyBudWxsKQoJCQkJCWlmKG5waCAmJiByZXNwb25zZUNodW5rZWQpIHsKCQkJCQkJLy9yYXdEYXRhT3V0cHV0KG1ha2VDaHVuayhjYXN0KGNvbnN0KHVieXRlKVtdKSB0KSk7CgkJCQkJCS8vIHdlJ3JlIG1ha2luZyB0aGUgY2h1bmsgaGVyZSBpbnN0ZWFkIG9mIGluIGEgZnVuY3Rpb24KCQkJCQkJLy8gdG8gYXZvaWQgdW5uZWVkZWQgZ2MgcHJlc3N1cmUKCQkJCQkJcmF3RGF0YU91dHB1dChjYXN0KGNvbnN0KHVieXRlKVtdKSB0b0hleCh0Lmxlbmd0aCkpOwoJCQkJCQlyYXdEYXRhT3V0cHV0KGNhc3QoY29uc3QodWJ5dGUpW10pICJcclxuIik7CgkJCQkJCXJhd0RhdGFPdXRwdXQoY2FzdChjb25zdCh1Ynl0ZSlbXSkgdCk7CgkJCQkJCXJhd0RhdGFPdXRwdXQoY2FzdChjb25zdCh1Ynl0ZSlbXSkgIlxyXG4iKTsKCgoJCQkJCX0gZWxzZSB7CgkJCQkJCXJhd0RhdGFPdXRwdXQoY2FzdChjb25zdCh1Ynl0ZSlbXSkgdCk7CgkJCQkJfQoJCQkJZWxzZQoJCQkJCXN0ZG91dC5yYXdXcml0ZSh0KTsKCQkJfQoJCX0KCgkJaWYobWF5YmVBdXRvQ2xvc2UgJiYgaXNBbGwpCgkJCWNsb3NlKCk7IC8vIGlmIHlvdSBzYXkgaXQgaXMgYWxsLCB0aGF0IG1lYW5zIHdlJ3JlIGRlZmluaXRlbHkgZG9uZQoJCQkJLy8gbWF5YmVBdXRvQ2xvc2UgY2FuIGJlIGZhbHNlIHRob3VnaCB0byBhdm9pZCB0aGlzIChpbXBvcnRhbnQgaWYgeW91IGNhbGwgZnJvbSBpbnNpZGUgY2xvc2UoKSEKCX0KCgl2b2lkIGZsdXNoKCkgewoJCWlmKHJhd0RhdGFPdXRwdXQgaXMgbnVsbCkKCQkJc3Rkb3V0LmZsdXNoKCk7CgkJZWxzZSBpZihmbHVzaERlbGVnYXRlICFpcyBudWxsKQoJCQlmbHVzaERlbGVnYXRlKCk7Cgl9CgoJdmVyc2lvbihhdXRvQnVmZmVyKQoJCWJvb2wgYXV0b0J1ZmZlciA9IHRydWU7CgllbHNlCgkJYm9vbCBhdXRvQnVmZmVyID0gZmFsc2U7Cgl1Ynl0ZVtdIG91dHB1dEJ1ZmZlcjsKCgkvLy8gRmx1c2hlcyB0aGUgYnVmZmVycyB0byB0aGUgbmV0d29yaywgc2lnbmlmeWluZyB0aGF0IHlvdSBhcmUgZG9uZS4KCS8vLyBZb3Ugc2hvdWxkIGFsd2F5cyBjYWxsIHRoaXMgZXhwbGljaXRseSB3aGVuIHlvdSBhcmUgZG9uZSBvdXRwdXR0aW5nIGRhdGEuCgl2b2lkIGNsb3NlKCkgewoJCWlmKGNsb3NlZCkKCQkJcmV0dXJuOyAvLyBkb24ndCBkb3VibGUgY2xvc2UKCgkJaWYoIW91dHB1dHRlZFJlc3BvbnNlRGF0YSkKCQkJd3JpdGUoIiIsIGZhbHNlLCBmYWxzZSk7CgoJCS8vIHdyaXRpbmcgYXV0byBidWZmZXJlZCBkYXRhCgkJaWYocmVxdWVzdE1ldGhvZCAhPSBSZXF1ZXN0TWV0aG9kLkhFQUQgJiYgYXV0b0J1ZmZlcikgewoJCQlpZighbnBoKQoJCQkJc3Rkb3V0LnJhd1dyaXRlKG91dHB1dEJ1ZmZlcik7CgkJCWVsc2UKCQkJCXdyaXRlKG91dHB1dEJ1ZmZlciwgdHJ1ZSwgZmFsc2UpOyAvLyB0ZWxsIGl0IHRoaXMgaXMgZXZlcnl0aGluZwoJCX0KCgkJLy8gY2xvc2luZyB0aGUgbGFzdCBjaHVuay4uLgoJCWlmKG5waCAmJiByYXdEYXRhT3V0cHV0ICFpcyBudWxsICYmIHJlc3BvbnNlQ2h1bmtlZCkKCQkJcmF3RGF0YU91dHB1dChjYXN0KGNvbnN0KHVieXRlKVtdKSAiMFxyXG5cclxuIik7CgoJCWlmKGZsdXNoRGVsZWdhdGUpCgkJCWZsdXNoRGVsZWdhdGUoKTsKCgkJY2xvc2VkID0gdHJ1ZTsKCX0KCgkvLyBDbG9zZXMgd2l0aG91dCBkb2luZyBhbnl0aGluZywgc2hvdWxkbid0IGJlIHVzZWQgb2Z0ZW4KCXZvaWQgcmF3Q2xvc2UoKSB7CgkJY2xvc2VkID0gdHJ1ZTsKCX0KCgkvKysKCQlHZXRzIGEgcmVxdWVzdCB2YXJpYWJsZSBhcyBhIHNwZWNpZmljIHR5cGUsIG9yIHRoZSBkZWZhdWx0IHZhbHVlIG9mIGl0IGlzbid0IHRoZXJlCgkJb3IgaXNuJ3QgY29udmVydGlibGUgdG8gdGhlIHJlcXVlc3QgdHlwZS4KCQkKCQlDaGVja3MgYm90aCBHRVQgYW5kIFBPU1QgdmFyaWFibGVzLCBwcmVmZXJyaW5nIHRoZSBQT1NUIHZhcmlhYmxlLCBpZiBhdmFpbGFibGUuCgoJCUEgbmljZSB0cmljayBpcyB1c2luZyB0aGUgZGVmYXVsdCB2YWx1ZSB0byBjaG9vc2UgdGhlIHR5cGU6CgoJCS0tLQoJCQkvKgoJCQkJVGhlIHJldHVybiB2YWx1ZSB3aWxsIG1hdGNoIHRoZSB0eXBlIG9mIHRoZSBkZWZhdWx0LgoJCQkJSGVyZSwgSSBnYXZlIDEwIGFzIGEgZGVmYXVsdCwgc28gdGhlIHJldHVybiB2YWx1ZSB3aWxsCgkJCQliZSBhbiBpbnQuCgoJCQkJSWYgdGhlIHVzZXItc3VwcGxpZWQgdmFsdWUgY2Fubm90IGJlIGNvbnZlcnRlZCB0byB0aGUKCQkJCXJlcXVlc3RlZCB0eXBlLCB5b3Ugd2lsbCBnZXQgdGhlIGRlZmF1bHQgdmFsdWUgYmFjay4KCQkJKi8KCQkJaW50IGEgPSBjZ2kucmVxdWVzdCgibnVtYmVyIiwgMTApOwoKCQkJaWYoY2dpLmdldFsibnVtYmVyIl0gPT0gIjExIikKCQkJCWFzc2VydChhID09IDExKTsgLy8gY29udmVyc2lvbiBzdWNjZWVkcwoKCQkJaWYoIm51bWJlciIgIWluIGNnaS5nZXQpCgkJCQlhc3NlcnQoYSA9PSAxMCk7IC8vIG5vIHZhbHVlIG1lYW5zIHlvdSBjYW4ndCBjb252ZXJ0IC0gZ2l2ZSB0aGUgZGVmYXVsdAoKCQkJaWYoY2dpLmdldFsibnVtYmVyIl0gPT0gInR3ZWx2ZSIpCgkJCQlhc3NlcnQoYSA9PSAxMCk7IC8vIGNvbnZlcnNpb24gZnJvbSBzdHJpbmcgdG8gaW50IHdvdWxkIGZhaWwsIHNvIHdlIGdldCB0aGUgZGVmYXVsdAoJCS0tLQoKCQlZb3UgY2FuIHVzZSBhbiBlbnVtIGFzIGFuIGVhc3kgd2hpdGVsaXN0LCB0b286CgoJCS0tLQoJCQllbnVtIE9wZXJhdGlvbnMgewoJCQkJYWRkLCByZW1vdmUsIHF1ZXJ5CgkJCX0KCgkJCWF1dG8gb3AgPSBjZ2kucmVxdWVzdCgib3AiLCBPcGVyYXRpb25zLnF1ZXJ5KTsKCgkJCWlmKGNnaS5nZXRbIm9wIl0gPT0gImFkZCIpCgkJCQlhc3NlcnQob3AgPT0gT3BlcmF0aW9ucy5hZGQpOwoJCQlpZihjZ2kuZ2V0WyJvcCJdID09ICJyZW1vdmUiKQoJCQkJYXNzZXJ0KG9wID09IE9wZXJhdGlvbnMucmVtb3ZlKTsKCQkJaWYoY2dpLmdldFsib3AiXSA9PSAicXVlcnkiKQoJCQkJYXNzZXJ0KG9wID09IE9wZXJhdGlvbnMucXVlcnkpOwoKCQkJaWYoY2dpLmdldFsib3AiXSA9PSAicmFuZG9tIHN0cmluZyIpCgkJCQlhc3NlcnQob3AgPT0gT3BlcmF0aW9ucy5xdWVyeSk7IC8vIHRoZSB2YWx1ZSBjYW4ndCBiZSBjb252ZXJ0ZWQgdG8gdGhlIGVudW0sIHNvIHdlIGdldCB0aGUgZGVmYXVsdAoJCS0tLQoJKy8KCVQgcmVxdWVzdChUID0gc3RyaW5nKShpbiBzdHJpbmcgbmFtZSwgaW4gVCBkZWYgPSBULmluaXQpIGNvbnN0IG5vdGhyb3cgewoJCXRyeSB7CgkJCXJldHVybgoJCQkJKG5hbWUgaW4gcG9zdCkgPyB0byFUKHBvc3RbbmFtZV0pIDoKCQkJCShuYW1lIGluIGdldCkgID8gdG8hVChnZXRbbmFtZV0pIDoKCQkJCWRlZjsKCQl9IGNhdGNoKEV4Y2VwdGlvbiBlKSB7IHJldHVybiBkZWY7IH0KCX0KCgkvLy8gSXMgdGhlIG91dHB1dCBhbHJlYWR5IGNsb3NlZD8KCWJvb2wgaXNDbG9zZWQoKSBjb25zdCB7CgkJcmV0dXJuIGNsb3NlZDsKCX0KCgkvKiBIb29rcyBmb3IgcmVkaXJlY3RpbmcgaW5wdXQgYW5kIG91dHB1dCAqLwoJcHJpdmF0ZSB2b2lkIGRlbGVnYXRlKGNvbnN0KHVieXRlKVtdKSByYXdEYXRhT3V0cHV0ID0gbnVsbDsKCXByaXZhdGUgdm9pZCBkZWxlZ2F0ZSgpIGZsdXNoRGVsZWdhdGUgPSBudWxsOwoKCS8qIFRoaXMgaW5mbyBpcyB1c2VkIHdoZW4gaGFuZGxpbmcgYSBtb3JlIHJhdyBIVFRQIHByb3RvY29sICovCglwcml2YXRlIGJvb2wgbnBoOwoJcHJpdmF0ZSBib29sIGh0dHAxMDsKCXByaXZhdGUgYm9vbCBjbG9zZWQ7Cglwcml2YXRlIGJvb2wgcmVzcG9uc2VDaHVua2VkID0gZmFsc2U7CgoJdmVyc2lvbihwcmVzZXJ2ZURhdGEpIC8vIG5vdGU6IHRoaXMgY2FuIGVhdCBsb3RzIG9mIG1lbW9yeTsgZG9uJ3QgdXNlIHVubGVzcyB5b3UncmUgc3VyZSB5b3UgbmVlZCBpdC4KCWltbXV0YWJsZSh1Ynl0ZSlbXSBvcmlnaW5hbFBvc3REYXRhOwoKCXB1YmxpYyBpbW11dGFibGUgc3RyaW5nIHBvc3RKc29uOwoKCS8qIEludGVybmFsIHN0YXRlIGZsYWdzICovCglwcml2YXRlIGJvb2wgb3V0cHV0dGVkUmVzcG9uc2VEYXRhOwoJcHJpdmF0ZSBib29sIG5vQ2FjaGUgPSB0cnVlOwoKCWNvbnN0KHN0cmluZ1tzdHJpbmddKSBlbnZpcm9ubWVudFZhcmlhYmxlczsKCgkvKiogV2hhdCBmb2xsb3dzIGlzIGRhdGEgZ290dGVuIGZyb20gdGhlIEhUVFAgcmVxdWVzdC4gSXQgaXMgYWxsIGZ1bGx5IGltbXV0YWJsZSwKCSAgICBwYXJ0aWFsbHkgYmVjYXVzZSBpdCBsb2dpY2FsbHkgaXMgKHlvdXIgY29kZSBkb2Vzbid0IGNoYW5nZSB3aGF0IHRoZSB1c2VyIHJlcXVlc3RlZC4uLikKCSAgICBhbmQgcGFydGlhbGx5IGJlY2F1c2UgSSBoYXRlIGhvdyBiYWQgcHJvZ3JhbXMgaW4gUEhQIGNoYW5nZSB0aG9zZSBzdXBlcmdsb2JhbHMgdG8gZG8KCSAgICBhbGwga2luZHMgb2YgaGFyZCB0byBmb2xsb3cgdWdsaW5lc3MuIEkgZG9uJ3Qgd2FudCB0aGF0IHRvIGV2ZXIgaGFwcGVuIGluIEQuCgoJICAgIEZvciBzb21lIG9mIHRoZXNlLCB5b3UnbGwgd2FudCB0byByZWZlciB0byB0aGUgaHR0cCBvciBjZ2kgc3BlY3MgZm9yIG1vcmUgZGV0YWlscy4KCSovCglpbW11dGFibGUoc3RyaW5nW3N0cmluZ10pIHJlcXVlc3RIZWFkZXJzOyAvLy8gQWxsIHRoZSByYXcgaGVhZGVycyBpbiB0aGUgcmVxdWVzdCBhcyBuYW1lL3ZhbHVlIHBhaXJzLiBUaGUgbmFtZSBpcyBzdG9yZWQgYXMgYWxsIGxvd2VyIGNhc2UsIGJ1dCBvdGhlcndpc2UgdGhlIHNhbWUgYXMgaXQgaXMgaW4gSFRUUDsgd29yZHMgc2VwYXJhdGVkIGJ5IGRhc2hlcy4gRm9yIGV4YW1wbGUsICJjb29raWUiIG9yICJhY2NlcHQtZW5jb2RpbmciLiBNYW55IEhUVFAgaGVhZGVycyBoYXZlIHNwZWNpYWxpemVkIHZhcmlhYmxlcyBiZWxvdyBmb3IgbW9yZSBjb252ZW5pZW5jZSBhbmQgc3RhdGljIG5hbWUgY2hlY2tpbmc7IHlvdSBzaG91bGQgZ2VuZXJhbGx5IHRyeSB0byB1c2UgdGhlbS4KCglpbW11dGFibGUoY2hhcltdKSBob3N0OyAJLy8vIFRoZSBob3N0bmFtZSBpbiB0aGUgcmVxdWVzdC4gSWYgb25lIHByb2dyYW0gc2VydmVzIG11bHRpcGxlIGRvbWFpbnMsIHlvdSBjYW4gdXNlIHRoaXMgdG8gZGlmZmVyZW50aWF0ZSBiZXR3ZWVuIHRoZW0uCglpbW11dGFibGUoY2hhcltdKSBvcmlnaW47IAkvLy8gVGhlIG9yaWdpbiBoZWFkZXIgaW4gdGhlIHJlcXVlc3QsIGlmIHByZXNlbnQuIFNvbWUgSFRNTDUgY3Jvc3MtZG9tYWluIGFwaXMgc2V0IHRoaXMgYW5kIHlvdSBzaG91bGQgY2hlY2sgaXQgb24gdGhvc2UgY3Jvc3MgZG9tYWluIHJlcXVlc3RzIGFuZCB3ZWJzb2NrZXRzLgoJaW1tdXRhYmxlKGNoYXJbXSkgdXNlckFnZW50OyAJLy8vIFRoZSBicm93c2VyJ3MgdXNlci1hZ2VudCBzdHJpbmcuIENhbiBiZSB1c2VkIHRvIGlkZW50aWZ5IHRoZSBicm93c2VyLgoJaW1tdXRhYmxlKGNoYXJbXSkgcGF0aEluZm87IAkvLy8gVGhpcyBpcyBhbnkgc3R1ZmYgc2VudCBhZnRlciB5b3VyIHByb2dyYW0ncyBuYW1lIG9uIHRoZSB1cmwsIGJ1dCBiZWZvcmUgdGhlIHF1ZXJ5IHN0cmluZy4gRm9yIGV4YW1wbGUsIHN1cHBvc2UgeW91ciBwcm9ncmFtIGlzIG5hbWVkICJhcHAiLiBJZiB0aGUgdXNlciBnb2VzIHRvIHNpdGUuY29tL2FwcCwgcGF0aEluZm8gaXMgZW1wdHkuIEJ1dCwgaGUgY2FuIGFsc28gZ28gdG8gc2l0ZS5jb20vYXBwL3NvbWUvc3ViL3BhdGg7IHRyZWF0aW5nIHlvdXIgcHJvZ3JhbSBsaWtlIGEgdmlydHVhbCBmb2xkZXIuIEluIHRoaXMgY2FzZSwgcGF0aEluZm8gPT0gIi9zb21lL3N1Yi9wYXRoIi4KCWltbXV0YWJsZShjaGFyW10pIHNjcmlwdE5hbWU7ICAgLy8vIFRoZSBmdWxsIGJhc2UgcGF0aCBvZiB5b3VyIHByb2dyYW0sIGFzIHNlZW4gYnkgdGhlIHVzZXIuIElmIHlvdXIgcHJvZ3JhbSBpcyBsb2NhdGVkIGF0IHNpdGUuY29tL3Byb2dyYW1zL2FwcHMsIHNjcmlwdE5hbWUgPT0gIi9wcm9ncmFtcy9hcHBzIi4KCWltbXV0YWJsZShjaGFyW10pIHNjcmlwdEZpbGVOYW1lOyAgIC8vLyBUaGUgcGh5c2ljYWwgZmlsZW5hbWUgb2YgeW91ciBzY3JpcHQKCWltbXV0YWJsZShjaGFyW10pIGF1dGhvcml6YXRpb247IC8vLyBUaGUgZnVsbCBhdXRob3JpemF0aW9uIHN0cmluZyBmcm9tIHRoZSBoZWFkZXIsIHVuZGlnZXN0ZWQuIFVzZWZ1bCBmb3IgaW1wbGVtZW50aW5nIGF1dGggc2NoZW1lcyBzdWNoIGFzIE9BdXRoIDEuMC4gTm90ZSB0aGF0IHNvbWUgd2ViIHNlcnZlcnMgZG8gbm90IGZvcndhcmQgdGhpcyB0byB0aGUgYXBwIHdpdGhvdXQgdGFraW5nIGV4dHJhIHN0ZXBzLiBTZWUgcmVxdWlyZUJhc2ljQXV0aCdzIGNvbW1lbnQgZm9yIG1vcmUgaW5mby4KCWltbXV0YWJsZShjaGFyW10pIGFjY2VwdDsgCS8vLyBUaGUgSFRUUCBhY2NlcHQgaGVhZGVyIGlzIHRoZSB1c2VyIGFnZW50IHRlbGxpbmcgd2hhdCBjb250ZW50IHR5cGVzIGl0IGlzIHdpbGxpbmcgdG8gYWNjZXB0LiBUaGlzIGlzIG9mdGVuICovKjsgdGhleSBhY2NlcHQgZXZlcnl0aGluZywgc28gaXQncyBub3QgdGVycmlibHkgdXNlZnVsLiAoVGhlIHNpbWlsYXIgc291bmRpbmcgQWNjZXB0LUVuY29kaW5nIGhlYWRlciBpcyBoYW5kbGVkIGF1dG9tYXRpY2FsbHkgZm9yIGNodW5raW5nIGFuZCBnemlwcGluZy4gU2ltcGx5IHNldCBnemlwUmVzcG9uc2UgPSB0cnVlIGFuZCBjZ2kuZCBoYW5kbGVzIHRoZSBkZXRhaWxzLCB6aXBwaW5nIGlmIHRoZSB1c2VyJ3MgYnJvd3NlciBpcyB3aWxsaW5nIHRvIGFjY2VwdCBpdC4KCWltbXV0YWJsZShjaGFyW10pIGxhc3RFdmVudElkOyAJLy8vIFRoZSBIVE1MIDUgZHJhZnQgaW5jbHVkZXMgYW4gRXZlbnRTb3VyY2UoKSBvYmplY3QgdGhhdCBjb25uZWN0cyB0byB0aGUgc2VydmVyLCBhbmQgcmVtYWlucyBvcGVuIHRvIHRha2UgYSBzdHJlYW0gb2YgZXZlbnRzLiBNeSBhcnNkLnJ0dWQgbW9kdWxlIGNhbiBoZWxwIHdpdGggdGhlIHNlcnZlciBzaWRlIHBhcnQgb2YgdGhhdC4gVGhlIExhc3QtRXZlbnQtSWQgaHR0cCBoZWFkZXIgaXMgZGVmaW5lZCBpbiB0aGUgZHJhZnQgdG8gaGVscCBoYW5kbGUgbG9zcyBvZiBjb25uZWN0aW9uLiBXaGVuIHRoZSBicm93c2VyIHJlY29ubmVjdHMgdG8geW91LCBpdCBzZXRzIHRoaXMgaGVhZGVyIHRvIHRoZSBsYXN0IGV2ZW50IGlkIGl0IHNhdywgc28geW91IGNhbiBjYXRjaCBpdCB1cC4gVGhpcyBtZW1iZXIgaGFzIHRoZSBjb250ZW50cyBvZiB0aGF0IGhlYWRlci4KCglpbW11dGFibGUoUmVxdWVzdE1ldGhvZCkgcmVxdWVzdE1ldGhvZDsgLy8vIFRoZSBIVFRQIHJlcXVlc3QgdmVyYjogR0VULCBQT1NULCBldGMuIEl0IGlzIHJlcHJlc2VudGVkIGFzIGFuIGVudW0gaW4gY2dpLmQgKHdoaWNoLCBsaWtlIG1hbnkgZW51bXMsIHlvdSBjYW4gY29udmVydCBiYWNrIHRvIHN0cmluZyB3aXRoIHN0ZC5jb252LnRvKCkpLiBBIEhUVFAgR0VUIGlzIHN1cHBvc2VkIHRvLCBhY2NvcmRpbmcgdG8gdGhlIHNwZWMsIG5vdCBoYXZlIHNpZGUgZWZmZWN0czsgYSB1c2VyIGNhbiBHRVQgc29tZXRoaW5nIG92ZXIgYW5kIG92ZXIgYWdhaW4gYW5kIGFsd2F5cyBoYXZlIHRoZSBzYW1lIHJlc3VsdC4gT24gYWxsIHJlcXVlc3RzLCB0aGUgZ2V0W10gYW5kIGdldEFycmF5W10gbWVtYmVycyBtYXkgYmUgZmlsbGVkIGluLiBUaGUgcG9zdFtdIGFuZCBwb3N0QXJyYXlbXSBtZW1iZXJzIGFyZSBvbmx5IGZpbGxlZCBpbiBvbiBQT1NUIG1ldGhvZHMuCglpbW11dGFibGUoY2hhcltdKSBxdWVyeVN0cmluZzsgCS8vLyBUaGUgdW5wYXJzZWQgY29udGVudCBvZiB0aGUgcmVxdWVzdCBxdWVyeSBzdHJpbmcgLSB0aGUgc3R1ZmYgYWZ0ZXIgdGhlID8gaW4geW91ciBVUkwuIFNlZSBnZXRbXSBhbmQgZ2V0QXJyYXlbXSBmb3IgYSBwYXJzZSB2aWV3IG9mIGl0LiBTb21ldGltZXMsIHRoZSB1bnBhcnNlZCBzdHJpbmcgaXMgdXNlZnVsIHRob3VnaCBpZiB5b3Ugd2FudCBhIGN1c3RvbSBmb3JtYXQgb2YgZGF0YSB1cCB0aGVyZSAocHJvYmFibHkgbm90IGEgZ29vZCBpZGVhLCB1bmxlc3MgaXQgaXMgcmVhbGx5IHNpbXBsZSwgbGlrZSAiP3VzZXJuYW1lIiBwZXJoYXBzLikKCWltbXV0YWJsZShjaGFyW10pIGNvb2tpZTsgCS8vLyBUaGUgdW5wYXJzZWQgY29udGVudCBvZiB0aGUgQ29va2llOiBoZWFkZXIgaW4gdGhlIHJlcXVlc3QuIFNlZSBhbHNvIHRoZSBjb29raWVzW3N0cmluZ10gbWVtYmVyIGZvciBhIHBhcnNlZCB2aWV3IG9mIHRoZSBkYXRhLgoJLyoqIFRoZSBSZWZlcmVyIGhlYWRlciBmcm9tIHRoZSByZXF1ZXN0LiAoSXQgaXMgbWlzc3BlbGxlZCBpbiB0aGUgSFRUUCBzcGVjLCBhbmQgdGh1cyB0aGUgYWN0dWFsIHJlcXVlc3QgYW5kIGNnaSBzcGVjcyB0b28sIGJ1dCBJIHNwZWxsZWQgdGhlIHdvcmQgY29ycmVjdGx5IGhlcmUgYmVjYXVzZSB0aGF0J3Mgc2FuZS4gVGhlIHNwZWMncyBtaXNzcGVsbGluZyBpcyBhbiBpbXBsZW1lbnRhdGlvbiBkZXRhaWwuKSBJdCBjb250YWlucyB0aGUgc2l0ZSB1cmwgdGhhdCByZWZlcnJlZCB0aGUgdXNlciB0byB5b3VyIHByb2dyYW07IHRoZSBzaXRlIHRoYXQgbGlua2VkIHRvIHlvdSwgb3IgaWYgeW91J3JlIHNlcnZpbmcgaW1hZ2VzLCB0aGUgc2l0ZSB0aGF0IGhhcyB5b3UgYXMgYW4gaW1hZ2UuIEFsc28sIGlmIHlvdSdyZSBpbiBhbiBpZnJhbWUsIHRoZSByZWZlcnJlciBpcyB0aGUgc2l0ZSB0aGF0IGlzIGZyYW1pbmcgeW91LgoKCUltcG9ydGFudCBub3RlOiBpZiB0aGUgdXNlciBjb3B5L3Bhc3RlcyB5b3VyIHVybCwgdGhpcyBpcyBibGFuaywgYW5kLCBqdXN0IGxpa2Ugd2l0aCBhbGwgb3RoZXIgdXNlciBkYXRhLCB0aGVpciBicm93c2VycyBjYW4gYWxzbyBsaWUgdG8geW91LiBEb24ndCByZWx5IG9uIGl0IGZvciByZWFsIHNlY3VyaXR5LgoJKi8KCWltbXV0YWJsZShjaGFyW10pIHJlZmVycmVyOwoJaW1tdXRhYmxlKGNoYXJbXSkgcmVxdWVzdFVyaTsgCS8vLyBUaGUgZnVsbCB1cmwgaWYgdGhlIGN1cnJlbnQgcmVxdWVzdCwgZXhjbHVkaW5nIHRoZSBwcm90b2NvbCBhbmQgaG9zdC4gcmVxdWVzdFVyaSA9PSBzY3JpcHROYW1lIH4gcGF0aEluZm8gfiAocXVlcnlTdHJpbmcubGVuZ3RoID8gIj8iIH4gcXVlcnlTdHJpbmcgOiAiIik7CgoJaW1tdXRhYmxlKGNoYXJbXSkgcmVtb3RlQWRkcmVzczsgLy8vIFRoZSBJUCBhZGRyZXNzIG9mIHRoZSB1c2VyLCBhcyB3ZSBzZWUgaXQuIChNaWdodCBub3QgbWF0Y2ggdGhlIElQIG9mIHRoZSB1c2VyJ3MgY29tcHV0ZXIgZHVlIHRvIHRoaW5ncyBsaWtlIHByb3hpZXMgYW5kIE5BVC4pIAoKCWltbXV0YWJsZSBib29sIGh0dHBzOyAJLy8vIFdhcyB0aGUgcmVxdWVzdCBlbmNyeXB0ZWQgdmlhIGh0dHBzPwoJaW1tdXRhYmxlIGludCBwb3J0OyAJLy8vIE9uIHdoYXQgVENQIHBvcnQgbnVtYmVyIGRpZCB0aGUgc2VydmVyIHJlY2VpdmUgdGhlIHJlcXVlc3Q/CgoJLyoqIEhlcmUgY29tZSB0aGUgcGFyc2VkIHJlcXVlc3QgdmFyaWFibGVzIC0gdGhlIHRoaW5ncyB0aGF0IGNvbWUgY2xvc2UgdG8gUEhQJ3MgX0dFVCwgX1BPU1QsIGV0Yy4gc3VwZXJnbG9iYWxzIGluIGNvbnRlbnQuICovCgoJaW1tdXRhYmxlKHN0cmluZ1tzdHJpbmddKSBnZXQ7IAkvLy8gVGhlIGRhdGEgZnJvbSB5b3VyIHF1ZXJ5IHN0cmluZyBpbiB0aGUgdXJsLCBvbmx5IHNob3dpbmcgdGhlIGxhc3Qgc3RyaW5nIG9mIGVhY2ggbmFtZS4gSWYgeW91IHdhbnQgdG8gaGFuZGxlIG11bHRpcGxlIHZhbHVlcyB3aXRoIHRoZSBzYW1lIG5hbWUsIHVzZSBnZXRBcnJheS4gVGhpcyBvbmx5IHdvcmtzIHJpZ2h0IGlmIHRoZSBxdWVyeSBzdHJpbmcgaXMgeC13d3ctZm9ybS11cmxlbmNvZGVkOyB0aGUgZGVmYXVsdCB5b3Ugc2VlIG9uIHRoZSB3ZWIgd2l0aCBuYW1lPXZhbHVlIHBhaXJzIHNlcGFyYXRlZCBieSB0aGUgJiBjaGFyYWN0ZXIuCglpbW11dGFibGUoc3RyaW5nW3N0cmluZ10pIHBvc3Q7IC8vLyBUaGUgZGF0YSBmcm9tIHRoZSByZXF1ZXN0J3MgYm9keSwgb24gUE9TVCByZXF1ZXN0cy4gSXQgcGFyc2VzIGFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCBkYXRhICh1c2VkIGJ5IG1vc3Qgd2ViIHJlcXVlc3RzLCBpbmNsdWRpbmcgdHlwaWNhbCBmb3JtcyksIGFuZCBtdWx0aXBhcnQvZm9ybS1kYXRhIHJlcXVlc3RzICh1c2VkIGJ5IGZpbGUgdXBsb2FkcyBvbiB3ZWIgZm9ybXMpIGludG8gdGhlIHNhbWUgY29udGFpbmVyLCBzbyB5b3UgY2FuIGFsd2F5cyBhY2Nlc3MgdGhlbSB0aGUgc2FtZSB3YXkuIEl0IG1ha2VzIG5vIGF0dGVtcHQgdG8gcGFyc2Ugb3RoZXIgY29udGVudCB0eXBlcy4gSWYgeW91IHdhbnQgdG8gYWNjZXB0IGFuIFhNTCBQb3N0IGJvZHkgKGZvciBhIHdlYiBhcGkgcGVyaGFwcyksIHlvdSdsbCBuZWVkIHRvIGhhbmRsZSB0aGUgcmF3IGRhdGEgeW91cnNlbGYuCglpbW11dGFibGUoc3RyaW5nW3N0cmluZ10pIGNvb2tpZXM7IC8vLyBTZXBhcmF0ZXMgb3V0IHRoZSBjb29raWUgaGVhZGVyIGludG8gaW5kaXZpZHVhbCBuYW1lL3ZhbHVlIHBhaXJzICh3aGljaCBpcyBob3cgeW91IHNldCB0aGVtISkKCgkvKioKCQlSZXByZXNlbnRzIHVzZXIgdXBsb2FkZWQgZmlsZXMuCgkJCgkJV2hlbiBtYWtpbmcgYSBmaWxlIHVwbG9hZCBmb3JtLCBiZSBzdXJlIHRvIGZvbGxvdyB0aGUgc3RhbmRhcmQ6IHNldCBtZXRob2Q9IlBPU1QiIGFuZCBlbmN0eXBlPSJtdWx0aXBhcnQvZm9ybS1kYXRhIiBpbiB5b3VyIGh0bWwgPGZvcm0+IHRhZyBhdHRyaWJ1dGVzLiBUaGUga2V5IGludG8gdGhpcyBhcnJheSBpcyB0aGUgbmFtZSBhdHRyaWJ1dGUgb24geW91ciBpbnB1dCB0YWcsIGp1c3QgbGlrZSB3aXRoIG90aGVyIHBvc3QgdmFyaWFibGVzLiBTZWUgdGhlIGNvbW1lbnRzIG9uIHRoZSBVcGxvYWRlZEZpbGUgc3RydWN0IGZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IHRoZSBkYXRhIGluc2lkZSwgaW5jbHVkaW5nIGltcG9ydGFudCBub3RlcyBvbiBtYXggc2l6ZSBhbmQgY29udGVudCBsb2NhdGlvbi4KCSovCglpbW11dGFibGUoVXBsb2FkZWRGaWxlW11bc3RyaW5nXSkgZmlsZXNBcnJheTsKCWltbXV0YWJsZShVcGxvYWRlZEZpbGVbc3RyaW5nXSkgZmlsZXM7CgoJLy8vIFVzZSB0aGVzZSBpZiB5b3UgZXhwZWN0IG11bHRpcGxlIGl0ZW1zIHN1Ym1pdHRlZCB3aXRoIHRoZSBzYW1lIG5hbWUuIGJ0dywgYXNzZXJ0KGdldFtuYW1lXSBpcyBnZXRBcnJheVtuYW1lXVskLTEpOyBzaG91bGQgcGFzcy4gU2FtZSBmb3IgcG9zdCBhbmQgY29va2llcy4KCS8vLyB0aGUgb3JkZXIgb2YgdGhlIGFycmF5cyBpcyB0aGUgb3JkZXIgdGhlIGRhdGEgYXJyaXZlcwoJaW1tdXRhYmxlKHN0cmluZ1tdW3N0cmluZ10pIGdldEFycmF5OyAvLy8gbGlrZSBnZXQsIGJ1dCBhbiBhcnJheSBvZiB2YWx1ZXMgcGVyIG5hbWUKCWltbXV0YWJsZShzdHJpbmdbXVtzdHJpbmddKSBwb3N0QXJyYXk7IC8vLyBkaXR0byBmb3IgcG9zdAoJaW1tdXRhYmxlKHN0cmluZ1tdW3N0cmluZ10pIGNvb2tpZXNBcnJheTsgLy8vIGRpdHRvIGZvciBjb29raWVzCgoJLy8gY29udmVuaWVuY2UgZnVuY3Rpb24gZm9yIGFwcGVuZGluZyB0byBhIHVyaSB3aXRob3V0IGV4dHJhID8KCS8vIG1hdGNoZXMgdGhlIG5hbWUgYW5kIGVmZmVjdCBvZiBqYXZhc2NyaXB0J3MgbG9jYXRpb24uc2VhcmNoIHByb3BlcnR5CglzdHJpbmcgc2VhcmNoKCkgY29uc3QgewoJCWlmKHF1ZXJ5U3RyaW5nLmxlbmd0aCkKCQkJcmV0dXJuICI/IiB+IHF1ZXJ5U3RyaW5nOwoJCXJldHVybiAiIjsKCX0KCgkvLyBGSVhNRTogd2hhdCBhYm91dCBtdWx0aXBsZSBmaWxlcyB3aXRoIHRoZSBzYW1lIG5hbWU/CiAgcHJpdmF0ZToKCS8vUmVxdWVzdE1ldGhvZCBfcmVxdWVzdE1ldGhvZDsKfQoKLy8vIHVzZSB0aGlzIGZvciB0ZXN0aW5nIG9yIG90aGVyIGlzb2xhdGVkIHRoaW5ncwpDZ2kgZHVtbXlDZ2koQ2dpLlJlcXVlc3RNZXRob2QgbWV0aG9kID0gQ2dpLlJlcXVlc3RNZXRob2QuR0VULCBzdHJpbmcgdXJsID0gbnVsbCwgaW4gdWJ5dGVbXSBkYXRhID0gbnVsbCwgdm9pZCBkZWxlZ2F0ZShjb25zdCh1Ynl0ZSlbXSkgb3V0cHV0U2luayA9IG51bGwpIHsKCS8vIHdlIHdhbnQgdG8gaWdub3JlLCBub3QgdXNlIHN0ZG91dAoJaWYob3V0cHV0U2luayBpcyBudWxsKQoJCW91dHB1dFNpbmsgPSBkZWxlZ2F0ZSB2b2lkKGNvbnN0KHVieXRlKVtdKSB7IH07CgoJc3RyaW5nW3N0cmluZ10gZW52OwoJZW52WyJSRVFVRVNUX01FVEhPRCJdID0gdG8hc3RyaW5nKG1ldGhvZCk7CgllbnZbIkNPTlRFTlRfTEVOR1RIIl0gPSB0byFzdHJpbmcoZGF0YS5sZW5ndGgpOwoKCWF1dG8gY2dpID0gbmV3IENnaSgKCQkwLAoJCWVudiwKCQl7IHJldHVybiBkYXRhOyB9LAoJCW91dHB1dFNpbmssCgkJbnVsbCk7CgoJcmV0dXJuIGNnaTsKfQoKCi8vIHNob3VsZCB0aGlzIGJlIGEgc2VwYXJhdGUgbW9kdWxlPyBQcm9iYWJseSwgYnV0IHRoYXQncyBhIGhhc3NsZS4KCi8vLyBNYWtlcyBhIGRhdGE6Ly8gdXJpIHRoYXQgY2FuIGJlIHVzZWQgYXMgbGlua3MgaW4gbW9zdCBuZXdlciBicm93c2VycyAoSUU4KykuCnN0cmluZyBtYWtlRGF0YVVybChzdHJpbmcgbWltZVR5cGUsIGluIHZvaWRbXSBkYXRhKSB7CglhdXRvIGRhdGE2NCA9IEJhc2U2NC5lbmNvZGUoY2FzdChjb25zdCh1Ynl0ZVtdKSkgZGF0YSk7CglyZXR1cm4gImRhdGE6IiB+IG1pbWVUeXBlIH4gIjtiYXNlNjQsIiB+IGFzc3VtZVVuaXF1ZShkYXRhNjQpOwp9CgovLyBGSVhNRTogSSBkb24ndCB0aGluayB0aGlzIGNsYXNzIGNvcnJlY3RseSBkZWNvZGVzL2VuY29kZXMgdGhlIGluZGl2aWR1YWwgcGFydHMKLy8vIFJlcHJlc2VudHMgYSB1cmwgdGhhdCBjYW4gYmUgYnJva2VuIGRvd24gb3IgYnVpbHQgdXAgdGhyb3VnaCBwcm9wZXJ0aWVzCnN0cnVjdCBVcmkgewoJYWxpYXMgdG9TdHJpbmcgdGhpczsgLy8gYmxhcmdoIGlkayBhIHVybCByZWFsbHkgaXMgYSBzdHJpbmcsIGJ1dCBzaG91bGQgaXQgYmUgaW1wbGljaXQ/CgoJLy8gc2NoZW1lLy91c2VyaW5mb0Bob3N0OnBvcnQvcGF0aD9xdWVyeSNmcmFnbWVudAoKCXN0cmluZyBzY2hlbWU7IC8vLyBlLmcuICJodHRwIiBpbiAiaHR0cDovL2V4YW1wbGUuY29tLyIKCXN0cmluZyB1c2VyaW5mbzsgLy8vIHRoZSB1c2VybmFtZSAoYW5kIHBvc3NpYmx5IGEgcGFzc3dvcmQpIGluIHRoZSB1cmkKCXN0cmluZyBob3N0OyAvLy8gdGhlIGRvbWFpbiBuYW1lCglpbnQgcG9ydDsgLy8vIHBvcnQgbnVtYmVyLCBpZiBnaXZlbi4gV2lsbCBiZSB6ZXJvIGlmIGEgcG9ydCB3YXMgbm90IGV4cGxpY2l0bHkgZ2l2ZW4KCXN0cmluZyBwYXRoOyAvLy8gZS5nLiAiL2ZvbGRlci9maWxlLmh0bWwiIGluICJodHRwOi8vZXhhbXBsZS5jb20vZm9sZGVyL2ZpbGUuaHRtbCIKCXN0cmluZyBxdWVyeTsgLy8vIHRoZSBzdHVmZiBhZnRlciB0aGUgPyBpbiBhIHVyaQoJc3RyaW5nIGZyYWdtZW50OyAvLy8gdGhlIHN0dWZmIGFmdGVyIHRoZSAjIGluIGEgdXJpLgoKCS8vIGlkayBpZiBpIHdhbnQgdG8ga2VlcCB0aGVzZSwgc2luY2UgdGhlIGZ1bmN0aW9ucyB0aGV5IHdyYXAgYXJlIHVzZWQgbWFueSwgbWFueSwgbWFueSB0aW1lcyBpbiBleGlzdGluZyBjb2RlLCBzbyB0aGlzIGlzIGVpdGhlciBhbiB1bm5lY2Vzc2FyeSBhbGlhcyBvciBhIGdyYXR1aXRvdXMgYnJlYWsgb2YgY29tcGF0aWJpbGl0eQoJLy8gdGhlIGRlY29kZSBvbmVzIG5lZWQgdG8ga2VlcCBkaWZmZXJlbnQgbmFtZXMgYW55d2F5IGJlY2F1c2Ugd2UgY2FuJ3Qgb3ZlcmxvYWQgb24gcmV0dXJuIHZhbHVlcy4uLgoJc3RhdGljIHN0cmluZyBlbmNvZGUoc3RyaW5nIHMpIHsgcmV0dXJuIHN0ZC51cmkuZW5jb2RlQ29tcG9uZW50KHMpOyB9CglzdGF0aWMgc3RyaW5nIGVuY29kZShzdHJpbmdbc3RyaW5nXSBzKSB7IHJldHVybiBlbmNvZGVWYXJpYWJsZXMocyk7IH0KCXN0YXRpYyBzdHJpbmcgZW5jb2RlKHN0cmluZ1tdW3N0cmluZ10gcykgeyByZXR1cm4gZW5jb2RlVmFyaWFibGVzKHMpOyB9CgoJLy8vIEJyZWFrcyBkb3duIGEgdXJpIHN0cmluZyB0byBpdHMgY29tcG9uZW50cwoJdGhpcyhzdHJpbmcgdXJpKSB7CgkJcmVwYXJzZSh1cmkpOwoJfQoKCXByaXZhdGUgdm9pZCByZXBhcnNlKHN0cmluZyB1cmkpIHsKCQlpbXBvcnQgc3RkLnJlZ2V4OwoJCS8vIGZyb20gUkZDIDM5ODYKCgkJLy8gdGhlIGN0UmVnZXggdHJpcGxlcyB0aGUgY29tcGlsZSB0aW1lIGFuZCBtYWtlcyB1Z2x5IGVycm9ycyBmb3Igbm8gcmVhbCBiZW5lZml0CgkJLy8gaXQgd2FzIGEgbmljZSBleHBlcmltZW50IGJ1dCBqdXN0IG5vdCB3b3J0aCBpdC4KCQkvLyBlbnVtIGN0ciA9IGN0UmVnZXghciJeKChbXjovPyNdKyk6KT8oLy8oW14vPyNdKikpPyhbXj8jXSopKFw/KFteI10qKSk/KCMoLiopKT8iOwoJCWF1dG8gY3RyID0gcmVnZXgociJeKChbXjovPyNdKyk6KT8oLy8oW14vPyNdKikpPyhbXj8jXSopKFw/KFteI10qKSk/KCMoLiopKT8iKTsKCgkJYXV0byBtID0gbWF0Y2godXJpLCBjdHIpOwoJCWlmKG0pIHsKCQkJc2NoZW1lID0gbS5jYXB0dXJlc1syXTsKCQkJYXV0byBhdXRob3JpdHkgPSBtLmNhcHR1cmVzWzRdOwoKCQkJYXV0byBpZHggPSBhdXRob3JpdHkuaW5kZXhPZigiQCIpOwoJCQlpZihpZHggIT0gLTEpIHsKCQkJCXVzZXJpbmZvID0gYXV0aG9yaXR5WzAgLi4gaWR4XTsKCQkJCWF1dGhvcml0eSA9IGF1dGhvcml0eVtpZHggKyAxIC4uICRdOwoJCQl9CgoJCQlpZHggPSBhdXRob3JpdHkuaW5kZXhPZigiOiIpOwoJCQlpZihpZHggPT0gLTEpIHsKCQkJCXBvcnQgPSAwOyAvLyAwIG1lYW5zIG5vdCBzcGVjaWZpZWQ7IHdlIHNob3VsZCB1c2UgdGhlIGRlZmF1bHQgZm9yIHRoZSBzY2hlbWUKCQkJCWhvc3QgPSBhdXRob3JpdHk7CgkJCX0gZWxzZSB7CgkJCQlob3N0ID0gYXV0aG9yaXR5WzAgLi4gaWR4XTsKCQkJCXBvcnQgPSB0byFpbnQoYXV0aG9yaXR5W2lkeCArIDEgLi4gJF0pOwoJCQl9CgoJCQlwYXRoID0gbS5jYXB0dXJlc1s1XTsKCQkJcXVlcnkgPSBtLmNhcHR1cmVzWzddOwoJCQlmcmFnbWVudCA9IG0uY2FwdHVyZXNbOV07CgkJfQoJCS8vIHVyaUludmFsaWRhdGVkID0gZmFsc2U7Cgl9CgoJcHJpdmF0ZSBzdHJpbmcgcmVidWlsZFVyaSgpIGNvbnN0IHsKCQlzdHJpbmcgcmV0OwoJCWlmKHNjaGVtZS5sZW5ndGgpCgkJCXJldCB+PSBzY2hlbWUgfiAiOiI7CgkJaWYodXNlcmluZm8ubGVuZ3RoIHx8IGhvc3QubGVuZ3RoKQoJCQlyZXQgfj0gIi8vIjsKCQlpZih1c2VyaW5mby5sZW5ndGgpCgkJCXJldCB+PSB1c2VyaW5mbyB+ICJAIjsKCQlpZihob3N0Lmxlbmd0aCkKCQkJcmV0IH49IGhvc3Q7CgkJaWYocG9ydCkKCQkJcmV0IH49ICI6IiB+IHRvIXN0cmluZyhwb3J0KTsKCgkJcmV0IH49IHBhdGg7CgoJCWlmKHF1ZXJ5Lmxlbmd0aCkKCQkJcmV0IH49ICI/IiB+IHF1ZXJ5OwoKCQlpZihmcmFnbWVudC5sZW5ndGgpCgkJCXJldCB+PSAiIyIgfiBmcmFnbWVudDsKCgkJLy8gdXJpID0gcmV0OwoJCS8vIHVyaUludmFsaWRhdGVkID0gZmFsc2U7CgkJcmV0dXJuIHJldDsKCX0KCgkvLy8gQ29udmVydHMgdGhlIGJyb2tlbiBkb3duIHBhcnRzIGJhY2sgaW50byBhIGNvbXBsZXRlIHN0cmluZwoJc3RyaW5nIHRvU3RyaW5nKCkgY29uc3QgewoJCS8vIGlmKHVyaUludmFsaWRhdGVkKQoJCQlyZXR1cm4gcmVidWlsZFVyaSgpOwoJfQoKCS8vLyBSZXR1cm5zIGEgbmV3IGFic29sdXRlIFVyaSBnaXZlbiBhIGJhc2UuIEl0IHRyZWF0cyB0aGlzIG9uZSBhcwoJLy8vIHJlbGF0aXZlIHdoZXJlIHBvc3NpYmxlLCBidXQgYWJzb2x1dGUgaWYgbm90LiAoSWYgcHJvdG9jb2wsIGRvbWFpbiwgb3IKCS8vLyBvdGhlciBpbmZvIGlzIG5vdCBzZXQsIHRoZSBuZXcgb25lIGluaGVyaXRzIGl0IGZyb20gdGhlIGJhc2UuKQoJLy8vCgkvLy8gQnJvd3NlcnMgdXNlIGEgZnVuY3Rpb24gbGlrZSB0aGlzIHRvIGZpZ3VyZSBvdXQgbGlua3MgaW4gaHRtbC4KCVVyaSBiYXNlZE9uKGluIFVyaSBiYXNlVXJsKSBjb25zdCB7CgkJVXJpIG4gPSB0aGlzOyAvLyBjb3BpZXMKCQkvLyBuLnVyaUludmFsaWRhdGVkID0gdHJ1ZTsgLy8gbWFrZSBzdXJlIHdlIHJlZ2VuZXJhdGUuLi4KCgkJLy8gdXNlcmluZm8gaXMgbm90IGluaGVyaXRlZC4uLiBpcyB0aGlzIHdyb25nPwoKCQkvLyBpZiBhbnl0aGluZyBpcyBnaXZlbiBpbiB0aGUgZXhpc3RpbmcgdXJsLCB3ZSBkb24ndCB1c2UgdGhlIGJhc2UgYW55bW9yZS4KCQlpZihuLnNjaGVtZS5lbXB0eSkgewoJCQluLnNjaGVtZSA9IGJhc2VVcmwuc2NoZW1lOwoJCQlpZihuLmhvc3QuZW1wdHkpIHsKCQkJCW4uaG9zdCA9IGJhc2VVcmwuaG9zdDsKCQkJCWlmKG4ucG9ydCA9PSAwKSB7CgkJCQkJbi5wb3J0ID0gYmFzZVVybC5wb3J0OwoJCQkJCWlmKG4ucGF0aC5sZW5ndGggPiAwICYmIG4ucGF0aFswXSAhPSAnLycpIHsKCQkJCQkJYXV0byBiID0gYmFzZVVybC5wYXRoWzAgLi4gYmFzZVVybC5wYXRoLmxhc3RJbmRleE9mKCIvIikgKyAxXTsKCQkJCQkJaWYoYi5sZW5ndGggPT0gMCkKCQkJCQkJCWIgPSAiLyI7CgkJCQkJCW4ucGF0aCA9IGIgfiBuLnBhdGg7CgkJCQkJfSBlbHNlIGlmKG4ucGF0aC5sZW5ndGggPT0gMCkgewoJCQkJCQluLnBhdGggPSBiYXNlVXJsLnBhdGg7CgkJCQkJfQoJCQkJfQoJCQl9CgkJfQoKCQlyZXR1cm4gbjsKCX0KCgkvLyBUaGlzIGNhbiBzb21ldGltZXMgYmUgYSBiaWcgcGFpbiBpbiB0aGUgYnV0dCBmb3IgbWUsIHNvIGxvdHMgb2YgY29weS9wYXN0ZSBoZXJlIHRvIGNvdmVyCgkvLyB0aGUgcG9zc2liaWxpdGllcy4KCXVuaXR0ZXN0IHsKCQlhdXRvIHVybCA9IFVyaSgiY29vbC5odG1sIik7IC8vIGNoZWNraW5nIHJlbGF0aXZlIGxpbmtzCgoJCWFzc2VydCh1cmwuYmFzZWRPbihVcmkoImh0dHA6Ly90ZXN0LmNvbS93aGF0L3Rlc3QuaHRtbCIpKSA9PSAiaHR0cDovL3Rlc3QuY29tL3doYXQvY29vbC5odG1sIik7CgkJYXNzZXJ0KHVybC5iYXNlZE9uKFVyaSgiaHR0cHM6Ly90ZXN0LmNvbS93aGF0L3Rlc3QuaHRtbCIpKSA9PSAiaHR0cHM6Ly90ZXN0LmNvbS93aGF0L2Nvb2wuaHRtbCIpOwoJCWFzc2VydCh1cmwuYmFzZWRPbihVcmkoImh0dHA6Ly90ZXN0LmNvbS93aGF0LyIpKSA9PSAiaHR0cDovL3Rlc3QuY29tL3doYXQvY29vbC5odG1sIik7CgkJYXNzZXJ0KHVybC5iYXNlZE9uKFVyaSgiaHR0cDovL3Rlc3QuY29tLyIpKSA9PSAiaHR0cDovL3Rlc3QuY29tL2Nvb2wuaHRtbCIpOwoJCWFzc2VydCh1cmwuYmFzZWRPbihVcmkoImh0dHA6Ly90ZXN0LmNvbSIpKSA9PSAiaHR0cDovL3Rlc3QuY29tL2Nvb2wuaHRtbCIpOwoJCWFzc2VydCh1cmwuYmFzZWRPbihVcmkoImh0dHA6Ly90ZXN0LmNvbS93aGF0L3Rlc3QuaHRtbD9hPWIiKSkgPT0gImh0dHA6Ly90ZXN0LmNvbS93aGF0L2Nvb2wuaHRtbCIpOwoJCWFzc2VydCh1cmwuYmFzZWRPbihVcmkoImh0dHA6Ly90ZXN0LmNvbS93aGF0L3Rlc3QuaHRtbD9hPWImYz1kIikpID09ICJodHRwOi8vdGVzdC5jb20vd2hhdC9jb29sLmh0bWwiKTsKCQlhc3NlcnQodXJsLmJhc2VkT24oVXJpKCJodHRwOi8vdGVzdC5jb20vd2hhdC90ZXN0Lmh0bWw/YT1iJmM9ZCN3aGF0IikpID09ICJodHRwOi8vdGVzdC5jb20vd2hhdC9jb29sLmh0bWwiKTsKCQlhc3NlcnQodXJsLmJhc2VkT24oVXJpKCJodHRwOi8vdGVzdC5jb20iKSkgPT0gImh0dHA6Ly90ZXN0LmNvbS9jb29sLmh0bWwiKTsKCgkJdXJsID0gVXJpKCIvc29tZXRoaW5nL2Nvb2wuaHRtbCIpOyAvLyBzYW1lIHNlcnZlciwgZGlmZmVyZW50IHBhdGgKCQlhc3NlcnQodXJsLmJhc2VkT24oVXJpKCJodHRwOi8vdGVzdC5jb20vd2hhdC90ZXN0Lmh0bWwiKSkgPT0gImh0dHA6Ly90ZXN0LmNvbS9zb21ldGhpbmcvY29vbC5odG1sIik7CgkJYXNzZXJ0KHVybC5iYXNlZE9uKFVyaSgiaHR0cHM6Ly90ZXN0LmNvbS93aGF0L3Rlc3QuaHRtbCIpKSA9PSAiaHR0cHM6Ly90ZXN0LmNvbS9zb21ldGhpbmcvY29vbC5odG1sIik7CgkJYXNzZXJ0KHVybC5iYXNlZE9uKFVyaSgiaHR0cDovL3Rlc3QuY29tL3doYXQvIikpID09ICJodHRwOi8vdGVzdC5jb20vc29tZXRoaW5nL2Nvb2wuaHRtbCIpOwoJCWFzc2VydCh1cmwuYmFzZWRPbihVcmkoImh0dHA6Ly90ZXN0LmNvbS8iKSkgPT0gImh0dHA6Ly90ZXN0LmNvbS9zb21ldGhpbmcvY29vbC5odG1sIik7CgkJYXNzZXJ0KHVybC5iYXNlZE9uKFVyaSgiaHR0cDovL3Rlc3QuY29tIikpID09ICJodHRwOi8vdGVzdC5jb20vc29tZXRoaW5nL2Nvb2wuaHRtbCIpOwoJCWFzc2VydCh1cmwuYmFzZWRPbihVcmkoImh0dHA6Ly90ZXN0LmNvbS93aGF0L3Rlc3QuaHRtbD9hPWIiKSkgPT0gImh0dHA6Ly90ZXN0LmNvbS9zb21ldGhpbmcvY29vbC5odG1sIik7CgkJYXNzZXJ0KHVybC5iYXNlZE9uKFVyaSgiaHR0cDovL3Rlc3QuY29tL3doYXQvdGVzdC5odG1sP2E9YiZjPWQiKSkgPT0gImh0dHA6Ly90ZXN0LmNvbS9zb21ldGhpbmcvY29vbC5odG1sIik7CgkJYXNzZXJ0KHVybC5iYXNlZE9uKFVyaSgiaHR0cDovL3Rlc3QuY29tL3doYXQvdGVzdC5odG1sP2E9YiZjPWQjd2hhdCIpKSA9PSAiaHR0cDovL3Rlc3QuY29tL3NvbWV0aGluZy9jb29sLmh0bWwiKTsKCQlhc3NlcnQodXJsLmJhc2VkT24oVXJpKCJodHRwOi8vdGVzdC5jb20iKSkgPT0gImh0dHA6Ly90ZXN0LmNvbS9zb21ldGhpbmcvY29vbC5odG1sIik7CgoJCXVybCA9IFVyaSgiP3F1ZXJ5PWFuc3dlciIpOyAvLyBzYW1lIHBhdGguIHNlcnZlciwgcHJvdG9jb2wsIGFuZCBwb3J0LCBqdXN0IGRpZmZlcmVudCBxdWVyeSBzdHJpbmcgYW5kIGZyYWdtZW50CgkJYXNzZXJ0KHVybC5iYXNlZE9uKFVyaSgiaHR0cDovL3Rlc3QuY29tL3doYXQvdGVzdC5odG1sIikpID09ICJodHRwOi8vdGVzdC5jb20vd2hhdC90ZXN0Lmh0bWw/cXVlcnk9YW5zd2VyIik7CgkJYXNzZXJ0KHVybC5iYXNlZE9uKFVyaSgiaHR0cHM6Ly90ZXN0LmNvbS93aGF0L3Rlc3QuaHRtbCIpKSA9PSAiaHR0cHM6Ly90ZXN0LmNvbS93aGF0L3Rlc3QuaHRtbD9xdWVyeT1hbnN3ZXIiKTsKCQlhc3NlcnQodXJsLmJhc2VkT24oVXJpKCJodHRwOi8vdGVzdC5jb20vd2hhdC8iKSkgPT0gImh0dHA6Ly90ZXN0LmNvbS93aGF0Lz9xdWVyeT1hbnN3ZXIiKTsKCQlhc3NlcnQodXJsLmJhc2VkT24oVXJpKCJodHRwOi8vdGVzdC5jb20vIikpID09ICJodHRwOi8vdGVzdC5jb20vP3F1ZXJ5PWFuc3dlciIpOwoJCWFzc2VydCh1cmwuYmFzZWRPbihVcmkoImh0dHA6Ly90ZXN0LmNvbSIpKSA9PSAiaHR0cDovL3Rlc3QuY29tP3F1ZXJ5PWFuc3dlciIpOwoJCWFzc2VydCh1cmwuYmFzZWRPbihVcmkoImh0dHA6Ly90ZXN0LmNvbS93aGF0L3Rlc3QuaHRtbD9hPWIiKSkgPT0gImh0dHA6Ly90ZXN0LmNvbS93aGF0L3Rlc3QuaHRtbD9xdWVyeT1hbnN3ZXIiKTsKCQlhc3NlcnQodXJsLmJhc2VkT24oVXJpKCJodHRwOi8vdGVzdC5jb20vd2hhdC90ZXN0Lmh0bWw/YT1iJmM9ZCIpKSA9PSAiaHR0cDovL3Rlc3QuY29tL3doYXQvdGVzdC5odG1sP3F1ZXJ5PWFuc3dlciIpOwoJCWFzc2VydCh1cmwuYmFzZWRPbihVcmkoImh0dHA6Ly90ZXN0LmNvbS93aGF0L3Rlc3QuaHRtbD9hPWImYz1kI3doYXQiKSkgPT0gImh0dHA6Ly90ZXN0LmNvbS93aGF0L3Rlc3QuaHRtbD9xdWVyeT1hbnN3ZXIiKTsKCQlhc3NlcnQodXJsLmJhc2VkT24oVXJpKCJodHRwOi8vdGVzdC5jb20iKSkgPT0gImh0dHA6Ly90ZXN0LmNvbT9xdWVyeT1hbnN3ZXIiKTsKCgkJdXJsID0gVXJpKCIjYW5jaG9yIik7IC8vIGV2ZXJ5dGhpbmcgc2hvdWxkIHJlbWFpbiB0aGUgc2FtZSBleGNlcHQgdGhlIGFuY2hvcgoKCQl1cmwgPSBVcmkoIi8vZXhhbXBsZS5jb20iKTsgLy8gc2FtZSBwcm90b2NvbCwgYnV0IGRpZmZlcmVudCBzZXJ2ZXIuIHRoZSBwYXRoIGhlcmUgc2hvdWxkIGJlIGJsYW5rLgoKCQl1cmwgPSBVcmkoIi8vZXhhbXBsZS5jb20vZXhhbXBsZS5odG1sIik7IC8vIHNhbWUgcHJvdG9jb2wsIGJ1dCBkaWZmZXJlbnQgc2VydmVyIGFuZCBwYXRoCgoJCXVybCA9IFVyaSgiaHR0cDovL2V4YW1wbGUuY29tL3Rlc3QuaHRtbCIpOyAvLyBjb21wbGV0ZWx5IGFic29sdXRlIGxpbmsgc2hvdWxkIG5ldmVyIGJlIG1vZGlmaWVkCgoJCXVybCA9IFVyaSgiaHR0cDovL2V4YW1wbGUuY29tIik7IC8vIGNvbXBsZXRlbHkgYWJzb2x1dGUgbGluayBzaG91bGQgbmV2ZXIgYmUgbW9kaWZpZWQsIGV2ZW4gaWYgaXQgaGFzIG5vIHBhdGgKCgkJLy8gRklYTUU6IGFkZCBzb21ldGhpbmcgZm9yIHBvcnQgdG9vCgl9CgoJLy8gdGhlc2UgYXJlIGxpa2UgamF2YXNjcmlwdCdzIGxvY2F0aW9uLnNlYXJjaCBhbmQgbG9jYXRpb24uaGFzaAoJc3RyaW5nIHNlYXJjaCgpIGNvbnN0IHsKCQlyZXR1cm4gcXVlcnkubGVuZ3RoID8gKCI/IiB+IHF1ZXJ5KSA6ICIiOwoJfQoJc3RyaW5nIGhhc2goKSBjb25zdCB7CgkJcmV0dXJuIGZyYWdtZW50Lmxlbmd0aCA/ICgiIyIgfiBmcmFnbWVudCkgOiAiIjsKCX0KfQoKCi8qCglmb3Igc2Vzc2lvbiwgc2VlIHdlYi5kCiovCgovLy8gYnJlYWtzIGRvd24gYSB1cmwgZW5jb2RlZCBzdHJpbmcKc3RyaW5nW11bc3RyaW5nXSBkZWNvZGVWYXJpYWJsZXMoc3RyaW5nIGRhdGEsIHN0cmluZyBzZXBhcmF0b3IgPSAiJiIpIHsKCWF1dG8gdmFycyA9IGRhdGEuc3BsaXQoc2VwYXJhdG9yKTsKCXN0cmluZ1tdW3N0cmluZ10gX2dldDsKCWZvcmVhY2godmFyOyB2YXJzKSB7CgkJYXV0byBlcXVhbCA9IHZhci5pbmRleE9mKCI9Iik7CgkJaWYoZXF1YWwgPT0gLTEpIHsKCQkJX2dldFtkZWNvZGVDb21wb25lbnQodmFyKV0gfj0gIiI7CgkJfSBlbHNlIHsKCQkJLy9fZ2V0W2RlY29kZUNvbXBvbmVudCh2YXJbMC4uZXF1YWxdKV0gfj0gZGVjb2RlQ29tcG9uZW50KHZhcltlcXVhbCArIDEgLi4gJF0ucmVwbGFjZSgiKyIsICIgIikpOwoJCQkvLyBzdHVwaWQgKyAtPiBzcGFjZSBjb252ZXJzaW9uLgoJCQlfZ2V0W2RlY29kZUNvbXBvbmVudCh2YXJbMC4uZXF1YWxdLnJlcGxhY2UoIisiLCAiICIpKV0gfj0gZGVjb2RlQ29tcG9uZW50KHZhcltlcXVhbCArIDEgLi4gJF0ucmVwbGFjZSgiKyIsICIgIikpOwoJCX0KCX0KCXJldHVybiBfZ2V0Owp9CgovLy8gYnJlYWtzIGRvd24gYSB1cmwgZW5jb2RlZCBzdHJpbmcsIGJ1dCBvbmx5IHJldHVybnMgdGhlIGxhc3QgdmFsdWUgb2YgYW55IGFycmF5CnN0cmluZ1tzdHJpbmddIGRlY29kZVZhcmlhYmxlc1NpbmdsZShzdHJpbmcgZGF0YSkgewoJc3RyaW5nW3N0cmluZ10gdmE7CglhdXRvIHZhckFycmF5ID0gZGVjb2RlVmFyaWFibGVzKGRhdGEpOwoJZm9yZWFjaChrLCB2OyB2YXJBcnJheSkKCQl2YVtrXSA9IHZbJC0xXTsKCglyZXR1cm4gdmE7Cn0KCi8vLyB1cmwgZW5jb2RlcyB0aGUgd2hvbGUgc3RyaW5nCnN0cmluZyBlbmNvZGVWYXJpYWJsZXMoaW4gc3RyaW5nW3N0cmluZ10gZGF0YSkgewoJc3RyaW5nIHJldDsKCglib29sIG91dHB1dHRlZCA9IGZhbHNlOwoJZm9yZWFjaChrLCB2OyBkYXRhKSB7CgkJaWYob3V0cHV0dGVkKQoJCQlyZXQgfj0gIiYiOwoJCWVsc2UKCQkJb3V0cHV0dGVkID0gdHJ1ZTsKCgkJcmV0IH49IHN0ZC51cmkuZW5jb2RlQ29tcG9uZW50KGspIH4gIj0iIH4gc3RkLnVyaS5lbmNvZGVDb21wb25lbnQodik7Cgl9CgoJcmV0dXJuIHJldDsKfQoKLy8vIHVybCBlbmNvZGVzIGEgd2hvbGUgc3RyaW5nCnN0cmluZyBlbmNvZGVWYXJpYWJsZXMoaW4gc3RyaW5nW11bc3RyaW5nXSBkYXRhKSB7CglzdHJpbmcgcmV0OwoKCWJvb2wgb3V0cHV0dGVkID0gZmFsc2U7Cglmb3JlYWNoKGssIGFycjsgZGF0YSkgewoJCWZvcmVhY2godjsgYXJyKSB7CgkJCWlmKG91dHB1dHRlZCkKCQkJCXJldCB+PSAiJiI7CgkJCWVsc2UKCQkJCW91dHB1dHRlZCA9IHRydWU7CgkJCXJldCB+PSBzdGQudXJpLmVuY29kZUNvbXBvbmVudChrKSB+ICI9IiB+IHN0ZC51cmkuZW5jb2RlQ29tcG9uZW50KHYpOwoJCX0KCX0KCglyZXR1cm4gcmV0Owp9CgovLy8gRW5jb2RlcyBhbGwgYnV0IHRoZSBleHBsaWNpdGx5IHVucmVzZXJ2ZWQgY2hhcmFjdGVycyBwZXIgcmZjIDM5ODYKLy8vIEFscGhhbnVtZXJpYyBhbmQgLV8ufiBhcmUgdGhlIG9ubHkgb25lcyBsZWZ0IHVuZW5jb2RlZAovLy8gbmFtZSBpcyBib3Jyb3dlZCBmcm9tIHBocApzdHJpbmcgcmF3dXJsZW5jb2RlKGluIGNoYXJbXSBkYXRhKSB7CglzdHJpbmcgcmV0OwoJcmV0LnJlc2VydmUoZGF0YS5sZW5ndGggKiAyKTsKCWZvcmVhY2goY2hhciBjOyBkYXRhKSB7CgkJaWYoCgkJCShjID49ICdhJyAmJiBjIDw9ICd6JykgfHwKCQkJKGMgPj0gJ0EnICYmIGMgPD0gJ1onKSB8fAoJCQkoYyA+PSAnMCcgJiYgYyA8PSAnOScpIHx8CgkJCWMgPT0gJy0nIHx8IGMgPT0gJ18nIHx8IGMgPT0gJy4nIHx8IGMgPT0gJ34nKQoJCXsKCQkJcmV0IH49IGM7CgkJfSBlbHNlIHsKCQkJcmV0IH49ICclJzsKCQkJLy8gc2luY2Ugd2UgaXRlcmF0ZSBvbiBjaGFyLCB0aGlzIHNob3VsZCBnaXZlIHVzIHRoZSBvY3RldHMgb2YgdGhlIGZ1bGwgdXRmOCBzdHJpbmcKCQkJcmV0IH49IHRvSGV4VXBwZXIoYyk7CgkJfQoJfQoKCXJldHVybiByZXQ7Cn0KCgovLyBodHRwIGhlbHBlciBmdW5jdGlvbnMKCi8vIGZvciBjaHVua2VkIHJlc3BvbnNlcyAod2hpY2ggZW1iZWRkZWQgaHR0cCBkb2VzIHdoZW5ldmVyIHBvc3NpYmxlKQp2ZXJzaW9uKG5vbmUpIC8vIHRoaXMgaXMgbW92ZWQgdXAgYWJvdmUgdG8gYXZvaWQgbWFraW5nIGEgY29weSBvZiB0aGUgZGF0YQpjb25zdCh1Ynl0ZSlbXSBtYWtlQ2h1bmsoY29uc3QodWJ5dGUpW10gZGF0YSkgewoJY29uc3QodWJ5dGUpW10gcmV0OwoKCXJldCA9IGNhc3QoY29uc3QodWJ5dGUpW10pIHRvSGV4KGRhdGEubGVuZ3RoKTsKCXJldCB+PSBjYXN0KGNvbnN0KHVieXRlKVtdKSAiXHJcbiI7CglyZXQgfj0gZGF0YTsKCXJldCB+PSBjYXN0KGNvbnN0KHVieXRlKVtdKSAiXHJcbiI7CgoJcmV0dXJuIHJldDsKfQoKc3RyaW5nIHRvSGV4KGxvbmcgbnVtKSB7CglzdHJpbmcgcmV0OwoJd2hpbGUobnVtKSB7CgkJaW50IHYgPSBudW0gJSAxNjsKCQludW0gLz0gMTY7CgkJY2hhciBkID0gY2FzdChjaGFyKSAoKHYgPCAxMCkgPyB2ICsgJzAnIDogKHYtMTApICsgJ2EnKTsKCQlyZXQgfj0gZDsKCX0KCglyZXR1cm4gdG8hc3RyaW5nKGFycmF5KHJldC5yZXRybykpOwp9CgpzdHJpbmcgdG9IZXhVcHBlcihsb25nIG51bSkgewoJc3RyaW5nIHJldDsKCXdoaWxlKG51bSkgewoJCWludCB2ID0gbnVtICUgMTY7CgkJbnVtIC89IDE2OwoJCWNoYXIgZCA9IGNhc3QoY2hhcikgKCh2IDwgMTApID8gdiArICcwJyA6ICh2LTEwKSArICdBJyk7CgkJcmV0IH49IGQ7Cgl9CgoJaWYocmV0Lmxlbmd0aCA9PSAxKQoJCXJldCB+PSAiMCI7IC8vIHVybCBlbmNvZGluZyByZXF1aXJlcyB0d28gZGlnaXRzIGFuZCB0aGF0J3Mgd2hhdCB0aGlzIGZ1bmN0aW9uIGlzIHVzZWQgZm9yLi4uCgoJcmV0dXJuIHRvIXN0cmluZyhhcnJheShyZXQucmV0cm8pKTsKfQoKCi8vIHRoZSBnZW5lcmljIG1peGlucwoKLy8vIFVzZSB0aGlzIGluc3RlYWQgb2Ygd3JpdGluZyB5b3VyIG93biBtYWluCm1peGluIHRlbXBsYXRlIEdlbmVyaWNNYWluKGFsaWFzIGZ1biwgbG9uZyBtYXhDb250ZW50TGVuZ3RoID0gZGVmYXVsdE1heENvbnRlbnRMZW5ndGgpIHsKCW1peGluIEN1c3RvbUNnaU1haW4hKENnaSwgZnVuLCBtYXhDb250ZW50TGVuZ3RoKTsKfQoKcHJpdmF0ZSBzdHJpbmcgc2ltcGxlSHRtbEVuY29kZShzdHJpbmcgcykgewoJcmV0dXJuIHMucmVwbGFjZSgiJiIsICImYW1wOyIpLnJlcGxhY2UoIjwiLCAiJmx0OyIpLnJlcGxhY2UoIj4iLCAiJmd0OyIpLnJlcGxhY2UoIlxuIiwgIjxiciAvPlxuIik7Cn0KCnN0cmluZyBtZXNzYWdlRnJvbUV4Y2VwdGlvbihUaHJvd2FibGUgdCkgewoJc3RyaW5nIG1lc3NhZ2U7CglpZih0ICFpcyBudWxsKSB7CgkJZGVidWcgbWVzc2FnZSA9IHQudG9TdHJpbmcoKTsKCQllbHNlICBtZXNzYWdlID0gIkFuIHVuZXhwZWN0ZWQgZXJyb3IgaGFzIG9jY3VycmVkLiI7Cgl9IGVsc2UgewoJCW1lc3NhZ2UgPSAiVW5rbm93biBlcnJvciI7Cgl9CglyZXR1cm4gbWVzc2FnZTsKfQoKc3RyaW5nIHBsYWluSHR0cEVycm9yKGJvb2wgaXNDZ2ksIHN0cmluZyB0eXBlLCBUaHJvd2FibGUgdCkgewoJYXV0byBtZXNzYWdlID0gbWVzc2FnZUZyb21FeGNlcHRpb24odCk7CgltZXNzYWdlID0gc2ltcGxlSHRtbEVuY29kZShtZXNzYWdlKTsKCglyZXR1cm4gZm9ybWF0KCIlcyAlc1xyXG5Db250ZW50LUxlbmd0aDogJXNcclxuXHJcbiVzIiwKCQlpc0NnaSA/ICJTdGF0dXM6IiA6ICJIVFRQLzEuMCIsCgkJdHlwZSwgbWVzc2FnZS5sZW5ndGgsIG1lc3NhZ2UpOwp9CgovLyByZXR1cm5zIHRydWUgaWYgd2Ugd2VyZSBhYmxlIHRvIHJlY292ZXIgcmVhc29uYWJseQpib29sIGhhbmRsZUV4Y2VwdGlvbihDZ2kgY2dpLCBUaHJvd2FibGUgdCkgewoJaWYoY2dpLmlzQ2xvc2VkKSB7CgkJLy8gaWYgdGhlIGNoYW5uZWwgaGFzIGJlZW4gZXhwbGljaXRseSBjbG9zZWQsIHdlIGNhbid0IGhhbmRsZSBpdCBoZXJlCgkJcmV0dXJuIHRydWU7Cgl9CgoJaWYoY2dpLm91dHB1dHRlZFJlc3BvbnNlRGF0YSkgewoJCS8vIHRoZSBoZWFkZXJzIGFyZSBzZW50LCBidXQgdGhlIGNoYW5uZWwgaXMgb3Blbi4uLiBzaW5jZSBpdCBjbG9zZXMgaWYgYWxsIHdhcyBzZW50LCB3ZSBjYW4gYXBwZW5kIGFuIGVycm9yIG1lc3NhZ2UgaGVyZS4KCQlyZXR1cm4gZmFsc2U7IC8vIGJ1dCBJIGRvbid0IHdhbnQgdG8sIHNpbmNlIEkgZG9uJ3Qga25vdyB3aGF0IGNvbmRpdGlvbiB0aGUgb3V0cHV0IGlzIGluOyBJIGRvbid0IHdhbnQgdG8gaW5qZWN0IHNvbWV0aGluZyAobm9yIGNoZWNrIHRoZSBjb250ZW50LXR5cGUgZm9yIHRoYXQgbWF0dGVyLiBTbyB3ZSBzYXkgaXQgd2FzIG5vdCBhIGNsZWFuIGhhbmRsaW5nLgoJfSBlbHNlIHsKCQkvLyBubyBoZWFkZXJzIGFyZSBzZW50LCB3ZSBjYW4gc2VuZCBhIGZ1bGwgYmxvd24gZXJyb3IgYW5kIHJlY292ZXIKCQljZ2kuc2V0Q2FjaGUoZmFsc2UpOwoJCWNnaS5zZXRSZXNwb25zZUNvbnRlbnRUeXBlKCJ0ZXh0L2h0bWwiKTsKCQljZ2kuc2V0UmVzcG9uc2VMb2NhdGlvbihudWxsKTsgLy8gY2FuY2VsIHRoZSByZWRpcmVjdAoJCWNnaS5zZXRSZXNwb25zZVN0YXR1cygiNTAwIEludGVybmFsIFNlcnZlciBFcnJvciIpOwoJCWNnaS53cml0ZShzaW1wbGVIdG1sRW5jb2RlKG1lc3NhZ2VGcm9tRXhjZXB0aW9uKHQpKSk7CgkJY2dpLmNsb3NlKCk7CgkJcmV0dXJuIHRydWU7Cgl9Cn0KCmJvb2wgaXNDZ2lSZXF1ZXN0TWV0aG9kKHN0cmluZyBzKSB7CglzID0gcy50b1VwcGVyKCk7CglpZihzID09ICJDT01NQU5ETElORSIpCgkJcmV0dXJuIHRydWU7Cglmb3JlYWNoKG1lbWJlcjsgX190cmFpdHMoYWxsTWVtYmVycywgQ2dpLlJlcXVlc3RNZXRob2QpKQoJCWlmKHMgPT0gbWVtYmVyKQoJCQlyZXR1cm4gdHJ1ZTsKCXJldHVybiBmYWxzZTsKfQoKLy8vIElmIHlvdSB3YW50IHRvIHVzZSBhIHN1YmNsYXNzIG9mIENnaSB3aXRoIGdlbmVyaWMgbWFpbiwgdXNlIHRoaXMgbWl4aW4uCm1peGluIHRlbXBsYXRlIEN1c3RvbUNnaU1haW4oQ3VzdG9tQ2dpLCBhbGlhcyBmdW4sIGxvbmcgbWF4Q29udGVudExlbmd0aCA9IGRlZmF1bHRNYXhDb250ZW50TGVuZ3RoKSBpZihpcyhDdXN0b21DZ2kgOiBDZ2kpKSB7CgkvLyBraW5kYSBoYWNreSAtIHRoZSBULi4uIGlzIHBhc3NlZCB0byBDZ2kncyBjb25zdHJ1Y3RvciBpbiBzdGFuZGFyZCBjZ2kgbW9kZSwgYW5kIGlnbm9yZWQgZWxzZXdoZXJlCgltaXhpbiBDdXN0b21DZ2lNYWluSW1wbCEoQ3VzdG9tQ2dpLCBmdW4sIG1heENvbnRlbnRMZW5ndGgpIGN1c3RvbUNnaU1haW5JbXBsXzsKCgl2b2lkIG1haW4oc3RyaW5nW10gYXJncykgewoJCWN1c3RvbUNnaU1haW5JbXBsXy5jZ2lNYWluSW1wbChhcmdzKTsKCX0KfQoKbWl4aW4gdGVtcGxhdGUgQ3VzdG9tQ2dpTWFpbkltcGwoQ3VzdG9tQ2dpLCBhbGlhcyBmdW4sIGxvbmcgbWF4Q29udGVudExlbmd0aCA9IGRlZmF1bHRNYXhDb250ZW50TGVuZ3RoKSBpZihpcyhDdXN0b21DZ2kgOiBDZ2kpKSB7Cgl2b2lkIGNnaU1haW5JbXBsKHN0cmluZ1tdIGFyZ3MpIHsKCgoJCS8vIHdlIHN1cHBvcnQgY29tbWFuZCBsaW5lIHRoaW5nIGZvciBlYXN5IHRlc3RpbmcgZXZlcnl3aGVyZQoJCS8vIGl0IG5lZWRzIHRvIGJlIGNhbGxlZCAuL2FwcCBtZXRob2QgdXJpIFtvdGhlciBhcmdzLi4uXQoJCWlmKGFyZ3MubGVuZ3RoID49IDMgJiYgaXNDZ2lSZXF1ZXN0TWV0aG9kKGFyZ3NbMV0pKSB7CgkJCUNnaSBjZ2kgPSBuZXcgQ3VzdG9tQ2dpKGFyZ3MpOwoJCQlzY29wZShleGl0KSBjZ2kuZGlzcG9zZSgpOwoJCQlmdW4oY2dpKTsKCQkJY2dpLmNsb3NlKCk7CgkJCXJldHVybjsKCQl9CgoKCQl1c2hvcnQgbGlzdGVuaW5nUG9ydCh1c2hvcnQgZGVmKSB7CgkJCWJvb2wgZm91bmQgPSBmYWxzZTsKCQkJZm9yZWFjaChhcmc7IGFyZ3MpIHsKCQkJCWlmKGZvdW5kKQoJCQkJCXJldHVybiB0byF1c2hvcnQoYXJnKTsKCQkJCWlmKGFyZyA9PSAiLS1wb3J0IiB8fCBhcmcgPT0gIi1wIiB8fCBhcmcgPT0gIi9wb3J0IiB8fCBhcmcgPT0gIi0tbGlzdGVuaW5nLXBvcnQiKQoJCQkJCWZvdW5kID0gdHJ1ZTsKCQkJfQoJCQlyZXR1cm4gZGVmOwoJCX0KCgkJc3RyaW5nIGxpc3RlbmluZ0hvc3QoKSB7CgkJCWJvb2wgZm91bmQgPSBmYWxzZTsKCQkJZm9yZWFjaChhcmc7IGFyZ3MpIHsKCQkJCWlmKGZvdW5kKQoJCQkJCXJldHVybiBhcmc7CgkJCQlpZihhcmcgPT0gIi0tbGlzdGVuaW5nLWhvc3QiIHx8IGFyZyA9PSAiLWgiIHx8IGFyZyA9PSAiL2xpc3RlbmluZy1ob3N0IikKCQkJCQlmb3VuZCA9IHRydWU7CgkJCX0KCQkJcmV0dXJuICIiOwoJCX0KCQl2ZXJzaW9uKG5ldG1hbl9odHRwZCkgewoJCQlpbXBvcnQgYXJzZC5odHRwZDsKCQkJLy8gd2hhdCBhYm91dCBmb3J3YXJkaW5nIHRoZSBvdGhlciBjb25zdHJ1Y3RvciBhcmdzPwoJCQkvLyB0aGlzIHByb2JhYmx5IG5lZWRzIGEgd2hvbGUgcmVkb2luZy4uLgoJCQlzZXJ2ZUh0dHAhQ3VzdG9tQ2dpKCZmdW4sIGxpc3RlbmluZ1BvcnQoODA4MCkpOy8vNTAwNSk7CgkJCXJldHVybjsKCQl9IGVsc2UKCQl2ZXJzaW9uKGVtYmVkZGVkX2h0dHBkX3Byb2Nlc3NlcykgewoJCQlpbXBvcnQgY29yZS5zeXMucG9zaXgudW5pc3RkOwoJCQkvL2ltcG9ydCBjb3JlLnN5cy5wb3NpeC5zeXMuc29ja2V0OwoJCQlpbXBvcnQgc3RkLmMubGludXguc29ja2V0OwoKCQkJaW50IHNvY2sgPSBzb2NrZXQoQUZfSU5FVCwgU09DS19TVFJFQU0sIDApOwoJCQlpZihzb2NrID09IC0xKQoJCQkJdGhyb3cgbmV3IEV4Y2VwdGlvbigic29ja2V0Iik7CgoJCQl7CgkJCQlzb2NrYWRkcl9pbiBhZGRyOwoJCQkJYWRkci5zaW5fZmFtaWx5ID0gQUZfSU5FVDsKCQkJCWFkZHIuc2luX3BvcnQgPSBodG9ucyhsaXN0ZW5pbmdQb3J0KDgwODUpKTsKCQkJCWF1dG8gbGggPSBsaXN0ZW5pbmdIb3N0KCk7CgkJCQlpZihsaC5sZW5ndGgpIHsKCQkJCQlpZihpbmV0X3B0b24oQUZfSU5FVCwgbGgudG9TdHJpbmd6KCksICZhZGRyLnNpbl9hZGRyLnNfYWRkcikgIT0gMSkKCQkJCQkJdGhyb3cgbmV3IEV4Y2VwdGlvbigiYmFkIGxpc3RlbmluZyBob3N0IGdpdmVuLCBwbGVhc2UgdXNlIGFuIElQIGFkZHJlc3MuXG5FeGFtcGxlOiAtLWxpc3RlbmluZy1ob3N0IDEyNy4wLjAuMSBtZWFucyBsaXN0ZW4gb25seSBvbiBMb2NhbGhvc3QuXG5FeGFtcGxlOiAtLWxpc3RlbmluZy1ob3N0IDAuMC4wLjAgbWVhbnMgbGlzdGVuIG9uIGFsbCBpbnRlcmZhY2VzLlxuT3IgeW91IGNhbiBwYXNzIGFueSBvdGhlciBzaW5nbGUgbnVtZXJpYyBJUHY0IGFkZHJlc3MuIik7CgkJCQl9IGVsc2UKCQkJCQlhZGRyLnNpbl9hZGRyLnNfYWRkciA9IElOQUREUl9BTlk7CgoJCQkJLy8gSEFDS0lTSAoJCQkJaW50IG9uID0gMTsKCQkJCXNldHNvY2tvcHQoc29jaywgU09MX1NPQ0tFVCwgU09fUkVVU0VBRERSLCAmb24sIG9uLnNpemVvZik7CgkJCQkvLyBlbmQgaGFjawoKCQkJCQoJCQkJaWYoYmluZChzb2NrLCBjYXN0KHNvY2thZGRyKikgJmFkZHIsIGFkZHIuc2l6ZW9mKSA9PSAtMSkgewoJCQkJCWNsb3NlKHNvY2spOwoJCQkJCXRocm93IG5ldyBFeGNlcHRpb24oImJpbmQiKTsKCQkJCX0KCgkJCQlpZihzb2NrLmxpc3RlbigxMjgpID09IC0xKSB7CgkJCQkJY2xvc2Uoc29jayk7CgkJCQkJdGhyb3cgbmV3IEV4Y2VwdGlvbigibGlzdGVuIik7CgkJCQl9CgkJCX0KCgoJCQlpbnQgcHJvY2Vzc0NvdW50OwoJCQlwaWRfdCBuZXdQaWQ7CgkJCXJlb3BlbjoKCQkJd2hpbGUocHJvY2Vzc0NvdW50IDwgOCkgewoJCQkJbmV3UGlkID0gZm9yaygpOwoJCQkJaWYobmV3UGlkID09IDApIHsKCQkJCQkvLyBzdGFydCBzZXJ2aW5nIG9uIHRoZSBzb2NrZXQKCQkJCQkvL3VieXRlWzQwOTZdIGJhY2tpbmdCdWZmZXI7CgkJCQkJZm9yKDs7KSB7CgkJCQkJCWJvb2wgY2xvc2VDb25uZWN0aW9uOwoJCQkJCQl1aW50IGk7CgkJCQkJCXNvY2thZGRyIGFkZHI7CgkJCQkJCWkgPSBhZGRyLnNpemVvZjsKCQkJCQkJaW50IHMgPSBhY2NlcHQoc29jaywgJmFkZHIsICZpKTsKCgkJCQkJCXRyeSB7CgoJCQkJCQkJaWYocyA9PSAtMSkKCQkJCQkJCQl0aHJvdyBuZXcgRXhjZXB0aW9uKCJhY2NlcHQiKTsKCgkJCQkJCQlzY29wZShmYWlsdXJlKSBjbG9zZShzKTsKCQkJCQkJCS8vdWJ5dGVbX190cmFpdHMoY2xhc3NJbnN0YW5jZVNpemUsIEJ1ZmZlcmVkSW5wdXRSYW5nZSldIGJ1ZmZlcmVkUmFuZ2VDb250YWluZXI7CgkJCQkJCQlhdXRvIGlyID0gbmV3IEJ1ZmZlcmVkSW5wdXRSYW5nZShzKTsKCQkJCQkJCS8vYXV0byBpciA9IGVtcGxhY2UhQnVmZmVyZWRJbnB1dFJhbmdlKGJ1ZmZlcmVkUmFuZ2VDb250YWluZXIsIHMsIGJhY2tpbmdCdWZmZXIpOwoKCQkJCQkJCXdoaWxlKCFpci5lbXB0eSkgewoJCQkJCQkJCXVieXRlW19fdHJhaXRzKGNsYXNzSW5zdGFuY2VTaXplLCBDdXN0b21DZ2kpXSBjZ2lDb250YWluZXI7CgoJCQkJCQkJCUNnaSBjZ2k7CgkJCQkJCQkJdHJ5IHsKCQkJCQkJCQkJY2dpID0gbmV3IEN1c3RvbUNnaShpciwgJmNsb3NlQ29ubmVjdGlvbik7CgkJCQkJCQkJCS8vY2dpID0gZW1wbGFjZSFDdXN0b21DZ2koY2dpQ29udGFpbmVyLCBpciwgJmNsb3NlQ29ubmVjdGlvbik7CgkJCQkJCQkJfSBjYXRjaChUaHJvd2FibGUgdCkgewoJCQkJCQkJCQkvLyBhIGNvbnN0cnVjdGlvbiBlcnJvciBpcyBlaXRoZXIgYmFkIGNvZGUgb3IgYmFkIHJlcXVlc3Q7IGJhZCByZXF1ZXN0IGlzIHdoYXQgaXQgc2hvdWxkIGJlIHNpbmNlIHRoaXMgaXMgYnVnIGZyZWUgOlAKCQkJCQkJCQkJLy8gYW55d2F5IGxldCdzIGtpbGwgdGhlIGNvbm5lY3Rpb24KCQkJCQkJCQkJc3RkZXJyLndyaXRlbG4odC50b1N0cmluZygpKTsKCQkJCQkJCQkJc2VuZEFsbChpci5zb3VyY2UsIHBsYWluSHR0cEVycm9yKGZhbHNlLCAiNDAwIEJhZCBSZXF1ZXN0IiwgdCkpOwoJCQkJCQkJCQljbG9zZUNvbm5lY3Rpb24gPSB0cnVlOwoJCQkJCQkJCQlicmVhazsKCQkJCQkJCQl9CgkJCQkJCQkJYXNzZXJ0KGNnaSAhaXMgbnVsbCk7CgkJCQkJCQkJc2NvcGUoZXhpdCkKCQkJCQkJCQkJY2dpLmRpc3Bvc2UoKTsKCgkJCQkJCQkJdHJ5IHsKCQkJCQkJCQkJZnVuKGNnaSk7CgkJCQkJCQkJCWNnaS5jbG9zZSgpOwoJCQkJCQkJCX0gY2F0Y2goQ29ubmVjdGlvbkV4Y2VwdGlvbiBjZSkgewoJCQkJCQkJCQljbG9zZUNvbm5lY3Rpb24gPSB0cnVlOwoJCQkJCQkJCX0gY2F0Y2goVGhyb3dhYmxlIHQpIHsKCQkJCQkJCQkJLy8gYSBwcm9jZXNzaW5nIGVycm9yIGNhbiBiZSByZWNvdmVyZWQgZnJvbQoJCQkJCQkJCQlzdGRlcnIud3JpdGVsbih0LnRvU3RyaW5nKTsKCQkJCQkJCQkJaWYoIWhhbmRsZUV4Y2VwdGlvbihjZ2ksIHQpKQoJCQkJCQkJCQkJY2xvc2VDb25uZWN0aW9uID0gdHJ1ZTsKCQkJCQkJCQl9CgoJCQkJCQkJCWlmKGNsb3NlQ29ubmVjdGlvbikgewoJCQkJCQkJCQlpci5zb3VyY2UuY2xvc2UoKTsKCQkJCQkJCQkJYnJlYWs7CgkJCQkJCQkJfSBlbHNlIHsKCQkJCQkJCQkJaWYoIWlyLmVtcHR5KQoJCQkJCQkJCQkJaXIucG9wRnJvbnQoKTsgLy8gZ2V0IHRoZSBuZXh0CgkJCQkJCQkJCWVsc2UgaWYoaXIuc291cmNlQ2xvc2VkKSB7CgkJCQkJCQkJCQlpci5zb3VyY2UuY2xvc2UoKTsKCQkJCQkJCQkJfQoJCQkJCQkJCX0KCQkJCQkJCX0KCgkJCQkJCQlpci5zb3VyY2UuY2xvc2UoKTsKCQkJCQkJfSBjYXRjaChUaHJvd2FibGUgdCkgewoJCQkJCQkJZGVidWcgd3JpdGVsbih0KTsKCQkJCQkJCS8vIG1vc3QgbGlrZWx5IGNhdXNlIGlzIGEgdGltZW91dAoJCQkJCQl9CgkJCQkJfQoJCQkJfSBlbHNlIHsKCQkJCQlwcm9jZXNzQ291bnQrKzsKCQkJCX0KCQkJfQoKCQkJLy8gdGhlIHBhcmVudCBzaG91bGQgd2FpdCBmb3IgaXRzIGNoaWxkcmVuLi4uCgkJCWlmKG5ld1BpZCkgewoJCQkJaW1wb3J0IGNvcmUuc3lzLnBvc2l4LnN5cy53YWl0OwoJCQkJaW50IHN0YXR1czsKCQkJCS8vIEZJWE1FOiBtYXliZSB3ZSBzaG91bGQgcmVzcGF3biBpZiBvbmUgZGllcyB1bmV4cGVjdGVkbHkKCQkJCXdoaWxlKC0xICE9IHdhaXQoJnN0YXR1cykpIHsKCQkJCWltcG9ydCBzdGQuc3RkaW87IHdyaXRlbG4oIlByb2Nlc3MgZGllZCAiLCBzdGF0dXMpOwoJCQkJCXByb2Nlc3NDb3VudC0tOwoJCQkJCWdvdG8gcmVvcGVuOwoJCQkJfQoJCQkJY2xvc2Uoc29jayk7CgkJCX0KCQl9IGVsc2UKCQl2ZXJzaW9uKGVtYmVkZGVkX2h0dHBkX3RocmVhZHMpIHsKCQkJYXV0byBtYW5hZ2VyID0gbmV3IExpc3RlbmluZ0Nvbm5lY3Rpb25NYW5hZ2VyKGxpc3RlbmluZ0hvc3QoKSwgbGlzdGVuaW5nUG9ydCg4MDg1KSwgJmRvVGhyZWFkSHR0cENvbm5lY3Rpb24hKEN1c3RvbUNnaSwgZnVuKSk7CgkJCW1hbmFnZXIubGlzdGVuKCk7CgkJfSBlbHNlCgkJdmVyc2lvbihzY2dpKSB7CgkJCWltcG9ydCBzdGQuZXhjZXB0aW9uOwoJCQlpbXBvcnQgYWwgPSBzdGQuYWxnb3JpdGhtOwoJCQlhdXRvIG1hbmFnZXIgPSBuZXcgTGlzdGVuaW5nQ29ubmVjdGlvbk1hbmFnZXIobGlzdGVuaW5nSG9zdCgpLCBsaXN0ZW5pbmdQb3J0KDQwMDApLCAmZG9UaHJlYWRTY2dpQ29ubmVjdGlvbiEoQ3VzdG9tQ2dpLCBmdW4sIG1heENvbnRlbnRMZW5ndGgpKTsKCQkJbWFuYWdlci5saXN0ZW4oKTsKCQl9IGVsc2UKCQl2ZXJzaW9uKGZhc3RjZ2kpIHsKCQkJLy8gICAgICAgICBTZXRIYW5kbGVyIGZjZ2lkLXNjcmlwdAoJCQlGQ0dYX1N0cmVhbSogaW5wdXQsIG91dHB1dCwgZXJyb3I7CgkJCUZDR1hfUGFyYW1BcnJheSBlbnY7CgoKCgkJCWNvbnN0KHVieXRlKVtdIGdldEZjZ2lDaHVuaygpIHsKCQkJCWNvbnN0KHVieXRlKVtdIHJldDsKCQkJCXdoaWxlKEZDR1hfSGFzU2VlbkVPRihpbnB1dCkgIT0gLTEpCgkJCQkJcmV0IH49IGNhc3QodWJ5dGUpIEZDR1hfR2V0Q2hhcihpbnB1dCk7CgkJCQlyZXR1cm4gcmV0OwoJCQl9CgoJCQl2b2lkIHdyaXRlRmNnaShjb25zdCh1Ynl0ZSlbXSBkYXRhKSB7CgkJCQlGQ0dYX1B1dFN0cihkYXRhLnB0ciwgZGF0YS5sZW5ndGgsIG91dHB1dCk7CgkJCX0KCgkJCXZvaWQgZG9BUmVxdWVzdCgpIHsKCQkJCXN0cmluZ1tzdHJpbmddIGZjZ2llbnY7CgoJCQkJZm9yKGF1dG8gZSA9IGVudjsgZSAhaXMgbnVsbCAmJiAqZSAhaXMgbnVsbDsgZSsrKSB7CgkJCQkJc3RyaW5nIGN1ciA9IHRvIXN0cmluZygqZSk7CgkJCQkJYXV0byBpZHggPSBjdXIuaW5kZXhPZigiPSIpOwoJCQkJCXN0cmluZyBuYW1lLCB2YWx1ZTsKCQkJCQlpZihpZHggPT0gLTEpCgkJCQkJCW5hbWUgPSBjdXI7CgkJCQkJZWxzZSB7CgkJCQkJCW5hbWUgPSBjdXJbMCAuLiBpZHhdOwoJCQkJCQl2YWx1ZSA9IGN1cltpZHggKyAxIC4uICRdOwoJCQkJCX0KCgkJCQkJZmNnaWVudltuYW1lXSA9IHZhbHVlOwoJCQkJfQoKCQkJCXZvaWQgZmx1c2hGY2dpKCkgewoJCQkJCUZDR1hfRkZsdXNoKG91dHB1dCk7CgkJCQl9CgoJCQkJQ2dpIGNnaTsKCQkJCXRyeSB7CgkJCQkJY2dpID0gbmV3IEN1c3RvbUNnaShtYXhDb250ZW50TGVuZ3RoLCBmY2dpZW52LCAmZ2V0RmNnaUNodW5rLCAmd3JpdGVGY2dpLCAmZmx1c2hGY2dpKTsKCQkJCX0gY2F0Y2goVGhyb3dhYmxlIHQpIHsKCQkJCQlGQ0dYX1B1dFN0cihjYXN0KHVieXRlKikgdC5tc2cucHRyLCB0Lm1zZy5sZW5ndGgsIGVycm9yKTsKCQkJCQl3cml0ZUZjZ2koY2FzdChjb25zdCh1Ynl0ZSlbXSkgcGxhaW5IdHRwRXJyb3IodHJ1ZSwgIjQwMCBCYWQgUmVxdWVzdCIsIHQpKTsKCQkJCQlyZXR1cm47IC8vY29udGludWU7CgkJCQl9CgkJCQlhc3NlcnQoY2dpICFpcyBudWxsKTsKCQkJCXNjb3BlKGV4aXQpIGNnaS5kaXNwb3NlKCk7CgkJCQl0cnkgewoJCQkJCWZ1bihjZ2kpOwoJCQkJCWNnaS5jbG9zZSgpOwoJCQkJfSBjYXRjaChUaHJvd2FibGUgdCkgewoJCQkJCS8vIGxvZyBpdCB0byB0aGUgZXJyb3Igc3RyZWFtCgkJCQkJRkNHWF9QdXRTdHIoY2FzdCh1Ynl0ZSopIHQubXNnLnB0ciwgdC5tc2cubGVuZ3RoLCBlcnJvcik7CgkJCQkJLy8gaGFuZGxlIGl0IGZvciB0aGUgdXNlciwgaWYgd2UgY2FuCgkJCQkJaWYoIWhhbmRsZUV4Y2VwdGlvbihjZ2ksIHQpKQoJCQkJCQlyZXR1cm47IC8vIGNvbnRpbnVlOwoJCQkJfQoJCQl9CgoJCQlhdXRvIGxwID0gbGlzdGVuaW5nUG9ydCgwKTsKCQkJRkNHWF9SZXF1ZXN0IHJlcXVlc3Q7CgkJCWlmKGxwKSB7CgkJCQkvLyBpZiBhIGxpc3RlbmluZyBwb3J0IHdhcyBzcGVjaWZpZWQgb24gdGhlIGNvbW1hbmQgbGluZSwgd2Ugd2FudCB0byBzcGF3biBvdXJzZWxmCgkJCQkvLyAobmVlZGVkIGZvciBuZ2lueCB3aXRob3V0IHNwYXduLWZjZ2ksIGUuZy4gb24gV2luZG93cykKCQkJCUZDR1hfSW5pdCgpOwoJCQkJYXV0byBzb2NrID0gRkNHWF9PcGVuU29ja2V0KHRvU3RyaW5neihsaXN0ZW5pbmdIb3N0KCkgfiAiOiIgfiB0byFzdHJpbmcobHApKSwgMTIpOwoJCQkJaWYoc29jayA8IDApCgkJCQkJdGhyb3cgbmV3IEV4Y2VwdGlvbigiQ291bGRuJ3QgbGlzdGVuIG9uIHRoZSBwb3J0Iik7CgkJCQlGQ0dYX0luaXRSZXF1ZXN0KCZyZXF1ZXN0LCBzb2NrLCAwKTsKCQkJCXdoaWxlKEZDR1hfQWNjZXB0X3IoJnJlcXVlc3QpID49IDApIHsKCQkJCQlpbnB1dCA9IHJlcXVlc3QuaW5TdHJlYW07CgkJCQkJb3V0cHV0ID0gcmVxdWVzdC5vdXRTdHJlYW07CgkJCQkJZXJyb3IgPSByZXF1ZXN0LmVyclN0cmVhbTsKCQkJCQllbnYgPSByZXF1ZXN0LmVudnA7CgkJCQkJZG9BUmVxdWVzdCgpOwoJCQkJfQoJCQl9IGVsc2UgewoJCQkJLy8gb3RoZXJ3aXNlLCBhc3N1bWUgdGhlIGh0dHBkIGlzIGRvaW5nIGl0ICh0aGUgY2FzZSBmb3IgQXBhY2hlLCBJSVMsIGFuZCBMaWdodHRwZCkKCQkJCS8vIHVzaW5nIHRoZSB2ZXJzaW9uIHdpdGggYSBnbG9iYWwgdmFyaWFibGUgc2luY2Ugd2UgYXJlIHNlcGFyYXRlIHByb2Nlc3NlcyBhbnl3YXkKCQkJCXdoaWxlKEZDR1hfQWNjZXB0KCZpbnB1dCwgJm91dHB1dCwgJmVycm9yLCAmZW52KSA+PSAwKSB7CgkJCQkJZG9BUmVxdWVzdCgpOwoJCQkJfQoJCQl9CgkJfSBlbHNlIHsKCQkJLy8gc3RhbmRhcmQgQ0dJIGlzIHRoZSBkZWZhdWx0IHZlcnNpb24KCQkJQ2dpIGNnaTsKCQkJdHJ5IHsKCQkJCWNnaSA9IG5ldyBDdXN0b21DZ2kobWF4Q29udGVudExlbmd0aCk7CgkJCX0gY2F0Y2goVGhyb3dhYmxlIHQpIHsKCQkJCXN0ZGVyci53cml0ZWxuKHQubXNnKTsKCQkJCS8vIHRoZSByZWFsIGh0dHAgc2VydmVyIHdpbGwgcHJvYmFibHkgaGFuZGxlIHRoaXM7CgkJCQkvLyBtb3N0IGxpa2VseSwgdGhpcyBpcyBhIGJ1ZyBpbiBDZ2kuIEJ1dCwgb2ggd2VsbC4KCQkJCXN0ZG91dC53cml0ZShwbGFpbkh0dHBFcnJvcih0cnVlLCAiNDAwIEJhZCBSZXF1ZXN0IiwgdCkpOwoJCQkJcmV0dXJuOwoJCQl9CgkJCWFzc2VydChjZ2kgIWlzIG51bGwpOwoJCQlzY29wZShleGl0KSBjZ2kuZGlzcG9zZSgpOwoKCQkJdHJ5IHsKCQkJCWZ1bihjZ2kpOwoJCQkJY2dpLmNsb3NlKCk7CgkJCX0gY2F0Y2ggKFRocm93YWJsZSB0KSB7CgkJCQlzdGRlcnIud3JpdGVsbih0Lm1zZyk7CgkJCQlpZighaGFuZGxlRXhjZXB0aW9uKGNnaSwgdCkpCgkJCQkJcmV0dXJuOwoJCQl9CgkJfQoJfQp9Cgp2ZXJzaW9uKGVtYmVkZGVkX2h0dHBkX3RocmVhZHMpCnZvaWQgZG9UaHJlYWRIdHRwQ29ubmVjdGlvbihDdXN0b21DZ2ksIGFsaWFzIGZ1bikoU29ja2V0IGNvbm5lY3Rpb24pIHsKCXNjb3BlKGZhaWx1cmUpIHsKCQkvLyBjYXRjaCBhbGwgZm9yIG90aGVyIGVycm9ycwoJCXNlbmRBbGwoY29ubmVjdGlvbiwgcGxhaW5IdHRwRXJyb3IoZmFsc2UsICI1MDAgSW50ZXJuYWwgU2VydmVyIEVycm9yIiwgbnVsbCkpOwoJCWNvbm5lY3Rpb24uY2xvc2UoKTsKCX0KCWJvb2wgY2xvc2VDb25uZWN0aW9uOwoJYXV0byBpciA9IG5ldyBCdWZmZXJlZElucHV0UmFuZ2UoY29ubmVjdGlvbik7CgoJd2hpbGUoIWlyLmVtcHR5KSB7CgkJQ2dpIGNnaTsKCQl0cnkgewoJCQljZ2kgPSBuZXcgQ3VzdG9tQ2dpKGlyLCAmY2xvc2VDb25uZWN0aW9uKTsKCQl9IGNhdGNoKENvbm5lY3Rpb25FeGNlcHRpb24gY2UpIHsKCQkJLy8gYnJva2VuIHBpcGUgb3Igc29tZXRoaW5nLCBqdXN0IGFib3J0IHRoZSBjb25uZWN0aW9uCgkJCWNsb3NlQ29ubmVjdGlvbiA9IHRydWU7CgkJCWJyZWFrOwoJCX0gY2F0Y2goVGhyb3dhYmxlIHQpIHsKCQkJLy8gYSBjb25zdHJ1Y3Rpb24gZXJyb3IgaXMgZWl0aGVyIGJhZCBjb2RlIG9yIGJhZCByZXF1ZXN0OyBiYWQgcmVxdWVzdCBpcyB3aGF0IGl0IHNob3VsZCBiZSBzaW5jZSB0aGlzIGlzIGJ1ZyBmcmVlIDpQCgkJCS8vIGFueXdheSBsZXQncyBraWxsIHRoZSBjb25uZWN0aW9uCgkJCXN0ZGVyci53cml0ZWxuKHQudG9TdHJpbmcoKSk7CgkJCXNlbmRBbGwoY29ubmVjdGlvbiwgcGxhaW5IdHRwRXJyb3IoZmFsc2UsICI0MDAgQmFkIFJlcXVlc3QiLCB0KSk7CgkJCWNsb3NlQ29ubmVjdGlvbiA9IHRydWU7CgkJCWJyZWFrOwoJCX0KCQlhc3NlcnQoY2dpICFpcyBudWxsKTsKCQlzY29wZShleGl0KQoJCQljZ2kuZGlzcG9zZSgpOwoKCQl0cnkgewoJCQlmdW4oY2dpKTsKCQkJY2dpLmNsb3NlKCk7CgkJfSBjYXRjaChDb25uZWN0aW9uRXhjZXB0aW9uIGNlKSB7CgkJCS8vIGJyb2tlbiBwaXBlIG9yIHNvbWV0aGluZywganVzdCBhYm9ydCB0aGUgY29ubmVjdGlvbgoJCQljbG9zZUNvbm5lY3Rpb24gPSB0cnVlOwoJCX0gY2F0Y2goVGhyb3dhYmxlIHQpIHsKCQkJLy8gYSBwcm9jZXNzaW5nIGVycm9yIGNhbiBiZSByZWNvdmVyZWQgZnJvbQoJCQlzdGRlcnIud3JpdGVsbih0LnRvU3RyaW5nKTsKCQkJaWYoIWhhbmRsZUV4Y2VwdGlvbihjZ2ksIHQpKQoJCQkJY2xvc2VDb25uZWN0aW9uID0gdHJ1ZTsKCQl9CgoJCWlmKGNsb3NlQ29ubmVjdGlvbikgewoJCQljb25uZWN0aW9uLmNsb3NlKCk7CgkJCWJyZWFrOwoJCX0gZWxzZSB7CgkJCWlmKCFpci5lbXB0eSkKCQkJCWlyLnBvcEZyb250KCk7IC8vIGdldCB0aGUgbmV4dAoJCQllbHNlIGlmKGlyLnNvdXJjZUNsb3NlZCkKCQkJCWlyLnNvdXJjZS5jbG9zZSgpOwoJCX0KCX0KCglpci5zb3VyY2UuY2xvc2UoKTsKfQoKdmVyc2lvbihzY2dpKQp2b2lkIGRvVGhyZWFkU2NnaUNvbm5lY3Rpb24oQ3VzdG9tQ2dpLCBhbGlhcyBmdW4sIGxvbmcgbWF4Q29udGVudExlbmd0aCkoU29ja2V0IGNvbm5lY3Rpb24pIHsKCS8vIGFuZCBub3cgd2UgY2FuIGJ1ZmZlcgoJc2NvcGUoZmFpbHVyZSkKCQljb25uZWN0aW9uLmNsb3NlKCk7CgoJaW1wb3J0IGFsID0gc3RkLmFsZ29yaXRobTsKCglzaXplX3Qgc2l6ZTsKCglzdHJpbmdbc3RyaW5nXSBoZWFkZXJzOwoKCWF1dG8gcmFuZ2UgPSBuZXcgQnVmZmVyZWRJbnB1dFJhbmdlKGNvbm5lY3Rpb24pOwoJbW9yZV9kYXRhOgoJYXV0byBjaHVuayA9IHJhbmdlLmZyb250KCk7CgkvLyB3YWl0aW5nIGZvciBjb2xvbiBmb3IgaGVhZGVyIGxlbmd0aAoJYXV0byBpZHggPSBpbmRleE9mKGNhc3Qoc3RyaW5nKSBjaHVuaywgJzonKTsKCWlmKGlkeCA9PSAtMSkgewoJCXJhbmdlLnBvcEZyb250KCk7CgkJZ290byBtb3JlX2RhdGE7Cgl9CgoJc2l6ZSA9IHRvIXNpemVfdChjYXN0KHN0cmluZykgY2h1bmtbMCAuLiBpZHhdKTsKCWNodW5rID0gcmFuZ2UuY29uc3VtZShpZHggKyAxKTsKCS8vIHJlYWRpbmcgaGVhZGVycwoJaWYoY2h1bmsubGVuZ3RoIDwgc2l6ZSkKCQlyYW5nZS5wb3BGcm9udCgwLCBzaXplICsgMSk7CgkvLyB3ZSBhcmUgbm93IGd1YXJhbnRlZWQgdG8gaGF2ZSBlbm91Z2gKCWNodW5rID0gcmFuZ2UuZnJvbnQoKTsKCWFzc2VydChjaHVuay5sZW5ndGggPiBzaXplKTsKCglpZHggPSAwOwoJc3RyaW5nIGtleTsKCXN0cmluZyB2YWx1ZTsKCWZvcmVhY2gocGFydDsgYWwuc3BsaXR0ZXIoY2h1bmssICdcMCcpKSB7CgkJaWYoaWR4ICYgMSkgeyAvLyBvZGQgaXMgdmFsdWUKCQkJdmFsdWUgPSBjYXN0KHN0cmluZykocGFydC5pZHVwKTsKCQkJaGVhZGVyc1trZXldID0gdmFsdWU7IC8vIGNvbW1pdAoJCX0gZWxzZQoJCQlrZXkgPSBjYXN0KHN0cmluZykocGFydC5pZHVwKTsKCQlpZHgrKzsKCX0KCgllbmZvcmNlKGNodW5rW3NpemVdID09ICcsJyk7IC8vIHRoZSB0ZXJtaW5hdG9yCgoJcmFuZ2UuY29uc3VtZShzaXplICsgMSk7CgkvLyByZWFkaW5nIGRhdGEKCS8vIHRoaXMgd2lsbCBiZSBkb25lIGJ5IENnaQoKCWNvbnN0KHVieXRlKVtdIGdldFNjZ2lDaHVuaygpIHsKCQkvLyB3ZSBhcmUgYWxyZWFkeSBwcmltZWQKCQlhdXRvIGRhdGEgPSByYW5nZS5mcm9udCgpOwoJCWlmKGRhdGEubGVuZ3RoID09IDAgJiYgIXJhbmdlLnNvdXJjZUNsb3NlZCkgewoJCQlyYW5nZS5wb3BGcm9udCgwKTsKCQkJZGF0YSA9IHJhbmdlLmZyb250KCk7CgkJfSBlbHNlIGlmIChyYW5nZS5zb3VyY2VDbG9zZWQpCgkJCXJhbmdlLnNvdXJjZS5jbG9zZSgpOwoKCQlyZXR1cm4gZGF0YTsKCX0KCgl2b2lkIHdyaXRlU2NnaShjb25zdCh1Ynl0ZSlbXSBkYXRhKSB7CgkJc2VuZEFsbChjb25uZWN0aW9uLCBkYXRhKTsKCX0KCgl2b2lkIGZsdXNoU2NnaSgpIHsKCQkvLyBJIGRvbid0ICp0aGluayogSSBoYXZlIHRvIGRvIGFueXRoaW5nLi4uLgoJfQoKCUNnaSBjZ2k7Cgl0cnkgewoJCWNnaSA9IG5ldyBDdXN0b21DZ2kobWF4Q29udGVudExlbmd0aCwgaGVhZGVycywgJmdldFNjZ2lDaHVuaywgJndyaXRlU2NnaSwgJmZsdXNoU2NnaSk7Cgl9IGNhdGNoKFRocm93YWJsZSB0KSB7CgkJc2VuZEFsbChjb25uZWN0aW9uLCBwbGFpbkh0dHBFcnJvcih0cnVlLCAiNDAwIEJhZCBSZXF1ZXN0IiwgdCkpOwoJCWNvbm5lY3Rpb24uY2xvc2UoKTsKCQlyZXR1cm47IC8vIHRoaXMgY29ubmVjdGlvbiBpcyBkZWFkCgl9Cglhc3NlcnQoY2dpICFpcyBudWxsKTsKCXNjb3BlKGV4aXQpIGNnaS5kaXNwb3NlKCk7Cgl0cnkgewoJCWZ1bihjZ2kpOwoJCWNnaS5jbG9zZSgpOwoJfSBjYXRjaChUaHJvd2FibGUgdCkgewoJCS8vIG5vIHN0ZCBlcnIKCQlpZighaGFuZGxlRXhjZXB0aW9uKGNnaSwgdCkpIHsKCQkJY29ubmVjdGlvbi5jbG9zZSgpOwoJCQlyZXR1cm47CgkJfQoJfQp9CgpzdHJpbmcgcHJpbnREYXRlKERhdGVUaW1lIGRhdGUpIHsKCXJldHVybiBmb3JtYXQoCgkJIiUuM3MsICUwMmQgJS4zcyAlZCAlMDJkOiUwMmQ6JTAyZCBHTVQiLCAvLyBjb3VsZCBiZSBVVEMgdG9vCgkJdG8hc3RyaW5nKGRhdGUuZGF5T2ZXZWVrKS5jYXBpdGFsaXplLAoJCWRhdGUuZGF5LAoJCXRvIXN0cmluZyhkYXRlLm1vbnRoKS5jYXBpdGFsaXplLAoJCWRhdGUueWVhciwKCQlkYXRlLmhvdXIsCgkJZGF0ZS5taW51dGUsCgkJZGF0ZS5zZWNvbmQpOwp9CgoKdmVyc2lvbih3aXRoX2NnaV9wYWNrZWQpIHsKLy8gVGhpcyBpcyB0ZW1wb3JhcnkgdW50aWwgUGhvYm9zIHN1cHBvcnRzIGJhc2U2NAppbW11dGFibGUodWJ5dGUpW10gYmFzZTY0VXJsRGVjb2RlKHN0cmluZyBlKSB7CglzdHJpbmcgZW5jb2RlZCA9IGUuaWR1cDsKCXdoaWxlIChlbmNvZGVkLmxlbmd0aCAlIDQpIHsKCQllbmNvZGVkIH49ICI9IjsgLy8gYWRkIHBhZGRpbmcKCX0KCgkvLyBjb252ZXJ0IGJhc2U2NCBVUkwgdG8gc3RhbmRhcmQgYmFzZSA2NAoJZW5jb2RlZCA9IGVuY29kZWQucmVwbGFjZSgiLSIsICIrIik7CgllbmNvZGVkID0gZW5jb2RlZC5yZXBsYWNlKCJfIiwgIi8iKTsKCglyZXR1cm4gY2FzdChpbW11dGFibGUodWJ5dGUpW10pIEJhc2U2NC5kZWNvZGUoZW5jb2RlZCk7Cn0KCS8vIHNob3VsZCBiZSBzZXQgYXMgYXJzZF9wYWNrZWRfZGF0YQoJc3RyaW5nIHBhY2tlZERhdGFFbmNvZGUoaW4gc3RyaW5nW3N0cmluZ10gdmFyaWFibGVzKSB7CgkJc3RyaW5nIHJlc3VsdDsKCgkJYm9vbCBvdXRwdXR0ZWQgPSBmYWxzZTsKCQlmb3JlYWNoKGssIHY7IHZhcmlhYmxlcykgewoJCQlpZihvdXRwdXR0ZWQpCgkJCQlyZXN1bHQgfj0gIiYiOwoJCQllbHNlCgkJCQlvdXRwdXR0ZWQgPSB0cnVlOwoKCQkJcmVzdWx0IH49IHN0ZC51cmkuZW5jb2RlQ29tcG9uZW50KGspIH4gIj0iIH4gc3RkLnVyaS5lbmNvZGVDb21wb25lbnQodik7CgkJfQoKCQlyZXN1bHQgPSBjYXN0KHN0cmluZykgQmFzZTY0LmVuY29kZShjYXN0KHVieXRlW10pIHJlc3VsdCk7CgoJCS8vIHVybCB2YXJpYW50CgkJcmVzdWx0LnJlcGxhY2UoIj0iLCAiIik7CgkJcmVzdWx0LnJlcGxhY2UoIisiLCAiLSIpOwoJCXJlc3VsdC5yZXBsYWNlKCIvIiwgIl8iKTsKCgkJcmV0dXJuIHJlc3VsdDsKCX0KfQoKCi8vIFJlZmVyZW5jaW5nIHRoaXMgZ2lnYW50aWMgdHlwZWlkIHNlZW1zIHRvIHJlbWluZCB0aGUgY29tcGlsZXIKLy8gdG8gYWN0dWFsbHkgcHV0IHRoZSBzeW1ib2wgaW4gdGhlIG9iamVjdCBmaWxlLiBJIGd1ZXNzIHRoZSBpbW11dGFibGUKLy8gYXNzb2MgYXJyYXkgYXJyYXkgaXNuJ3QgYWN0dWFsbHkgaW5jbHVkZWQgaW4gZHJ1bnRpbWUKdm9pZCBoYWNrQXJvdW5kTGlua2VyRXJyb3IoKSB7CiAgICAgIHdyaXRlbG4odHlwZWlkKGNvbnN0KGltbXV0YWJsZShjaGFyKVtdW10pW2ltbXV0YWJsZShjaGFyKVtdXSkpOwogICAgICB3cml0ZWxuKHR5cGVpZChpbW11dGFibGUoY2hhcilbXVtdW2ltbXV0YWJsZShjaGFyKVtdXSkpOwogICAgICB3cml0ZWxuKHR5cGVpZChDZ2kuVXBsb2FkZWRGaWxlW2ltbXV0YWJsZShjaGFyKVtdXSkpOwogICAgICB3cml0ZWxuKHR5cGVpZChDZ2kuVXBsb2FkZWRGaWxlW11baW1tdXRhYmxlKGNoYXIpW11dKSk7CiAgICAgIHdyaXRlbG4odHlwZWlkKGltbXV0YWJsZShDZ2kuVXBsb2FkZWRGaWxlKVtpbW11dGFibGUoY2hhcilbXV0pKTsKICAgICAgd3JpdGVsbih0eXBlaWQoaW1tdXRhYmxlKENnaS5VcGxvYWRlZEZpbGVbXSlbaW1tdXRhYmxlKGNoYXIpW11dKSk7CiAgICAgIHdyaXRlbG4odHlwZWlkKGltbXV0YWJsZShjaGFyW10pW2ltbXV0YWJsZShjaGFyKVtdXSkpOwogICAgICAvLyB0aGlzIGlzIGdldHRpbmcga2luZGEgcmlkaWN1bG91cyBidHcuIE1vdmluZyBhc3NvYyBhcnJheXMKICAgICAgLy8gdG8gdGhlIGxpYnJhcnkgaXMgdGhlIHBhaW4gdGhhdCBrZWVwcyBvbiBjb21pbmcuCgogICAgICAvLyBlaCB0aGlzIGJyb2tlIHRoZSBidWlsZCBvbiB0aGUgd29yayBzZXJ2ZXIKICAgICAgLy8gd3JpdGVsbih0eXBlaWQoaW1tdXRhYmxlKGNoYXIpW11baW1tdXRhYmxlKHN0cmluZ1tdKV0pKTsKICAgICAgd3JpdGVsbih0eXBlaWQoaW1tdXRhYmxlKHN0cmluZ1tdKVtpbW11dGFibGUoY2hhcilbXV0pKTsKfQoKCgoKCnZlcnNpb24oZmFzdGNnaSkgewoJcHJhZ21hKGxpYiwgImZjZ2kiKTsKCglzdGF0aWMgaWYoc2l6ZV90LnNpemVvZiA9PSA4KSAvLyA2NCBiaXQKCQlhbGlhcyBsb25nIGNfaW50OwoJZWxzZQoJCWFsaWFzIGludCBjX2ludDsKCglleHRlcm4oQykgewoJCXN0cnVjdCBGQ0dYX1N0cmVhbSB7CgkJCXVieXRlKiByZE5leHQ7CgkJCXVieXRlKiB3ck5leHQ7CgkJCXVieXRlKiBzdG9wOwoJCQl1Ynl0ZSogc3RvcFVuZ2V0OwoJCQljX2ludCBpc1JlYWRlcjsKCQkJY19pbnQgaXNDbG9zZWQ7CgkJCWNfaW50IHdhc0ZDbG9zZUNhbGxlZDsKCQkJY19pbnQgRkNHSV9lcnJubzsKCQkJdm9pZCogZnVuY3Rpb24oRkNHWF9TdHJlYW0qIHN0cmVhbSkgZmlsbEJ1ZmZQcm9jOwoJCQl2b2lkKiBmdW5jdGlvbihGQ0dYX1N0cmVhbSogc3RyZWFtLCBjX2ludCBkb0Nsb3NlKSBlbXB0eUJ1ZmZQcm9jOwoJCQl2b2lkKiBkYXRhOwoJCX0KCgkJLy8gbm90ZTogdGhpcyBpcyBtZWFudCB0byBiZSBvcGFxdWUsIHNvIGRvbid0IGFjY2VzcyBpdCBkaXJlY3RseQoJCXN0cnVjdCBGQ0dYX1JlcXVlc3QgewoJCQlpbnQgcmVxdWVzdElkOwoJCQlpbnQgcm9sZTsKCQkJRkNHWF9TdHJlYW0qIGluU3RyZWFtOwoJCQlGQ0dYX1N0cmVhbSogb3V0U3RyZWFtOwoJCQlGQ0dYX1N0cmVhbSogZXJyU3RyZWFtOwoJCQljaGFyKiogZW52cDsKCQkJdm9pZCogcGFyYW1zUHRyOwoJCQlpbnQgaXBjRmQ7CgkJCWludCBpc0JlZ2luUHJvY2Vzc2VkOwoJCQlpbnQga2VlcENvbm5lY3Rpb247CgkJCWludCBhcHBTdGF0dXM7CgkJCWludCBuV3JpdGVyczsKCQkJaW50IGZsYWdzOwoJCQlpbnQgbGlzdGVuX3NvY2s7CgkJfQoKCQlpbnQgRkNHWF9Jbml0UmVxdWVzdChGQ0dYX1JlcXVlc3QgKnJlcXVlc3QsIGludCBzb2NrLCBpbnQgZmxhZ3MpOwoJCXZvaWQgRkNHWF9Jbml0KCk7CgoJCWludCBGQ0dYX0FjY2VwdF9yKEZDR1hfUmVxdWVzdCAqcmVxdWVzdCk7CgoKCQlhbGlhcyBjaGFyKiogRkNHWF9QYXJhbUFycmF5OwoKCQljX2ludCBGQ0dYX0FjY2VwdChGQ0dYX1N0cmVhbSoqIHN0ZGluLCBGQ0dYX1N0cmVhbSoqIHN0ZG91dCwgRkNHWF9TdHJlYW0qKiBzdGRlcnIsIEZDR1hfUGFyYW1BcnJheSogZW52cCk7CgkJY19pbnQgRkNHWF9HZXRDaGFyKEZDR1hfU3RyZWFtKiBzdHJlYW0pOwoJCWNfaW50IEZDR1hfUHV0U3RyKGNvbnN0IHVieXRlKiBzdHIsIGNfaW50IG4sIEZDR1hfU3RyZWFtKiBzdHJlYW0pOwoJCWludCBGQ0dYX0hhc1NlZW5FT0YoRkNHWF9TdHJlYW0qIHN0cmVhbSk7CgkJY19pbnQgRkNHWF9GRmx1c2goRkNHWF9TdHJlYW0gKnN0cmVhbSk7CgoJCWludCBGQ0dYX09wZW5Tb2NrZXQoaW4gY2hhciosIGludCk7Cgl9Cn0KCgovKiBUaGlzIG1pZ2h0IGdvIGludCBhIHNlcGFyYXRlIG1vZHVsZSBldmVudHVhbGx5LiBJdCBpcyBhIG5ldHdvcmsgaW5wdXQgaGVscGVyIGNsYXNzLiAqLwoKaW1wb3J0IHN0ZC5zb2NrZXQ7CgovLyBpdCBpcyBhIGNsYXNzIHByaW1hcmlseSBmb3IgcmVmZXJlbmNlIHNlbWFudGljcwovLyBJIG1pZ2h0IGNoYW5nZSB0aGlzIGludGVyZmFjZQovLy8KY2xhc3MgQnVmZmVyZWRJbnB1dFJhbmdlIHsKCXZlcnNpb24oUG9zaXgpCgl0aGlzKGludCBzb3VyY2UsIHVieXRlW10gYnVmZmVyID0gbnVsbCkgewoJCXRoaXMobmV3IFNvY2tldChjYXN0KHNvY2tldF90KSBzb3VyY2UsIEFkZHJlc3NGYW1pbHkuSU5FVCksIGJ1ZmZlcik7Cgl9CgoJdGhpcyhTb2NrZXQgc291cmNlLCB1Ynl0ZVtdIGJ1ZmZlciA9IG51bGwpIHsKCQkvLyBpZiB0aGV5IGNvbm5lY3QgYnV0IG5ldmVyIHNlbmQgc3R1ZmYgdG8gdXMsIHdlIGRvbid0IHdhbnQgaXQgd2FzdGluZyB0aGUgcHJvY2VzcwoJCS8vIHNvIHNldHRpbmcgYSB0aW1lIG91dAoJCXNvdXJjZS5zZXRPcHRpb24oU29ja2V0T3B0aW9uTGV2ZWwuU09DS0VULCBTb2NrZXRPcHRpb24uUkNWVElNRU8sIGR1ciEic2Vjb25kcyIoMykpOwoJCXRoaXMuc291cmNlID0gc291cmNlOwoJCWlmKGJ1ZmZlciBpcyBudWxsKSB7CgkJCXVuZGVybHlpbmdCdWZmZXIgPSBuZXcgdWJ5dGVbNDA5Nl07CgkJCWFsbG93R3Jvd3RoID0gdHJ1ZTsKCQl9IGVsc2UgewoJCQl1bmRlcmx5aW5nQnVmZmVyID0gYnVmZmVyOwoJCX0KCgkJYXNzZXJ0KHVuZGVybHlpbmdCdWZmZXIubGVuZ3RoKTsKCgkJLy8gd2UgYXNzdW1lIHZpZXcucHRyIGlzIGFsd2F5cyBpbnNpZGUgdW5kZXJseWluZ0J1ZmZlcgoJCXZpZXcgPSB1bmRlcmx5aW5nQnVmZmVyWzAgLi4gMF07CgoJCXBvcEZyb250KCk7IC8vIHByaW1lCgl9CgoJLyoqCgkJQSBzbGlnaHQgZGlmZmVyZW5jZSBmcm9tIHJlZ3VsYXIgcmFuZ2VzIGlzIHlvdSBjYW4gZ2l2ZSBpdCB0aGUgbWF4aW11bQoJCW51bWJlciBvZiBieXRlcyB0byBjb25zdW1lLgoKCQlJTVBPUlRBTlQgTk9URTogdGhlIGRlZmF1bHQgaXMgdG8gY29uc3VtZSBub3RoaW5nLCBzbyBpZiB5b3UgZG9uJ3QgY2FsbAoJCWNvbnN1bWUoKSB5b3Vyc2VsZiBhbmQgdXNlIGEgcmVndWxhciBmb3JlYWNoLCBpdCB3aWxsIGluZmluaXRlbHkgbG9vcCEKCgkJVGhlIGRlZmF1bHQgaXMgdG8gZG8gd2hhdCBhIG5vcm1hbCByYW5nZSBkb2VzLCBhbmQgY29uc3VtZSB0aGUgd2hvbGUgYnVmZmVyCgkJYW5kIHdhaXQgZm9yIGFkZGl0aW9uYWwgaW5wdXQuCgoJCVlvdSBjYW4gYWxzbyBzcGVjaWZ5IDAsIHRvIGFwcGVuZCB0byB0aGUgYnVmZmVyLCBvciBhbnkgb3RoZXIgbnVtYmVyCgkJdG8gcmVtb3ZlIHRoZSBmcm9udCBuIGJ5dGVzIGFuZCB3YWl0IGZvciBtb3JlLgoJKi8KCXZvaWQgcG9wRnJvbnQoc2l6ZV90IG1heEJ5dGVzVG9Db25zdW1lID0gMCAvKnNpemVfdC5tYXgqLywgc2l6ZV90IG1pbkJ5dGVzVG9TZXR0bGVGb3IgPSAwLCBib29sIHNraXBDb25zdW1lID0gZmFsc2UpIHsKCQlpZihzb3VyY2VDbG9zZWQpCgkJCXRocm93IG5ldyBFeGNlcHRpb24oImNhbid0IGdldCBhbnkgbW9yZSBkYXRhIGZyb20gYSBjbG9zZWQgc291cmNlIik7CgkJaWYoIXNraXBDb25zdW1lKQoJCQljb25zdW1lKG1heEJ5dGVzVG9Db25zdW1lKTsKCgkJLy8gd2UgbWlnaHQgaGF2ZSB0byBncm93IHRoZSBidWZmZXIKCQlpZihtaW5CeXRlc1RvU2V0dGxlRm9yID4gdW5kZXJseWluZ0J1ZmZlci5sZW5ndGggfHwgdmlldy5sZW5ndGggPT0gdW5kZXJseWluZ0J1ZmZlci5sZW5ndGgpIHsKCQkJaWYoYWxsb3dHcm93dGgpIHsKCQkJCWF1dG8gdmlld1N0YXJ0ID0gdmlldy5wdHIgLSB1bmRlcmx5aW5nQnVmZmVyLnB0cjsKCQkJCXNpemVfdCBncm93dGggPSA0MDk2OwoJCQkJLy8gbWFrZSBzdXJlIHdlIGhhdmUgZW5vdWdoIGZvciB3aGF0IHdlJ3JlIGJlaW5nIGFza2VkIGZvcgoJCQkJaWYobWluQnl0ZXNUb1NldHRsZUZvciAtIHVuZGVybHlpbmdCdWZmZXIubGVuZ3RoID4gZ3Jvd3RoKQoJCQkJCWdyb3d0aCA9IG1pbkJ5dGVzVG9TZXR0bGVGb3IgLSB1bmRlcmx5aW5nQnVmZmVyLmxlbmd0aDsKCQkJCXVuZGVybHlpbmdCdWZmZXIubGVuZ3RoICs9IGdyb3d0aDsKCQkJCXZpZXcgPSB1bmRlcmx5aW5nQnVmZmVyW3ZpZXdTdGFydCAuLiB2aWV3Lmxlbmd0aF07CgkJCX0gZWxzZQoJCQkJdGhyb3cgbmV3IEV4Y2VwdGlvbigiTm8gcm9vbSBsZWZ0IGluIHRoZSBidWZmZXIiKTsKCQl9CgoJCWRvIHsKCQkJYXV0byBmcmVlU3BhY2UgPSB1bmRlcmx5aW5nQnVmZmVyW3VuZGVybHlpbmdCdWZmZXIucHRyIC0gdmlldy5wdHIgKyB2aWV3Lmxlbmd0aCAuLiAkXTsKCQkJdHJ5X2FnYWluOgoJCQlhdXRvIHJldCA9IHNvdXJjZS5yZWNlaXZlKGZyZWVTcGFjZSk7CgkJCWlmKHJldCA9PSBTb2NrZXQuRVJST1IpIHsKCQkJCXZlcnNpb24oUG9zaXgpIHsKCQkJCQlpbXBvcnQgY29yZS5zdGRjLmVycm5vOwoJCQkJCWlmKGVycm5vID09IEVJTlRSIHx8IGVycm5vID09IEVBR0FJTikgewoJCQkJCQlnb3RvIHRyeV9hZ2FpbjsKCQkJCQl9CgkJCQl9CgkJCQl0aHJvdyBuZXcgRXhjZXB0aW9uKGxhc3RTb2NrZXRFcnJvcik7IC8vIEZJWE1FCgkJCX0KCQkJaWYocmV0ID09IDApIHsKCQkJCXNvdXJjZUNsb3NlZCA9IHRydWU7CgkJCQlyZXR1cm47CgkJCX0KCgkJCXZpZXcgPSB1bmRlcmx5aW5nQnVmZmVyW3VuZGVybHlpbmdCdWZmZXIucHRyIC0gdmlldy5wdHIgLi4gdmlldy5sZW5ndGggKyByZXRdOwoJCX0gd2hpbGUodmlldy5sZW5ndGggPCBtaW5CeXRlc1RvU2V0dGxlRm9yKTsKCX0KCgkvLy8gUmVtb3ZlcyBuIGJ5dGVzIGZyb20gdGhlIGZyb250IG9mIHRoZSBidWZmZXIsIGFuZCByZXR1cm5zIHRoZSBuZXcgYnVmZmVyIHNsaWNlLgoJLy8vIFlvdSBtaWdodCB3YW50IHRvIGlkdXAgdGhlIGRhdGEgeW91IGFyZSBjb25zdW1pbmcgaWYgeW91IHN0b3JlIGl0LCBzaW5jZSBpdCBtYXkKCS8vLyBiZSBvdmVyd3JpdHRlbiBvbiB0aGUgbmV3IHBvcEZyb250LgoJLy8vCgkvLy8gWW91IGRvIG5vdCBuZWVkIHRvIGNhbGwgdGhpcyBpZiB5b3UgYWx3YXlzIHdhbnQgdG8gd2FpdCBmb3IgbW9yZSBkYXRhIHdoZW4geW91CgkvLy8gY29uc3VtZSBzb21lLgoJdWJ5dGVbXSBjb25zdW1lKHNpemVfdCBieXRlcykgewoJCXZpZXcgPSB2aWV3W2J5dGVzID4gJCA/ICQgOiBieXRlcyAuLiAkXTsKCQlpZih2aWV3Lmxlbmd0aCA9PSAwKSB7CgkJCXZpZXcgPSB1bmRlcmx5aW5nQnVmZmVyWzAgLi4gMF07IC8vIGdvIGFoZWFkIGFuZCByZXVzZSB0aGUgYmVnaW5uaW5nCgkJCS8qCgkJCXdyaXRlbG4oIkhFUkUiKTsKCQkJcG9wRnJvbnQoMCwgMCwgdHJ1ZSk7IC8vIHRyeSB0byBsb2FkIG1vcmUgaWYgd2UgY2FuLCBjaGVja3MgaWYgdGhlIHNvdXJjZSBpcyBjbG9zZWQKCQkJd3JpdGVsbihjYXN0KHN0cmluZylmcm9udCk7CgkJCXdyaXRlbG4oIkRPTkUiKTsKCQkJKi8KCQl9CgkJcmV0dXJuIGZyb250OwoJfQoKCWJvb2wgZW1wdHkoKSB7CgkJcmV0dXJuIHNvdXJjZUNsb3NlZCAmJiB2aWV3Lmxlbmd0aCA9PSAwOwoJfQoKCXVieXRlW10gZnJvbnQoKSB7CgkJcmV0dXJuIHZpZXc7Cgl9CgoJaW52YXJpYW50KCkgewoJCWFzc2VydCh2aWV3LnB0ciA+PSB1bmRlcmx5aW5nQnVmZmVyLnB0cik7CgkJLy8gaXQgc2hvdWxkIG5ldmVyIGJlIGVxdWFsLCBzaW5jZSBpZiB0aGF0IGhhcHBlbnMgdmlldyBvdWdodCB0byBiZSBlbXB0eSwgYW5kIHRodXMgcmV1c2luZyB0aGUgYnVmZmVyCgkJYXNzZXJ0KHZpZXcucHRyIDwgdW5kZXJseWluZ0J1ZmZlci5wdHIgKyB1bmRlcmx5aW5nQnVmZmVyLmxlbmd0aCk7Cgl9CgoJdWJ5dGVbXSB1bmRlcmx5aW5nQnVmZmVyOwoJYm9vbCBhbGxvd0dyb3d0aDsKCXVieXRlW10gdmlldzsKCVNvY2tldCBzb3VyY2U7Cglib29sIHNvdXJjZUNsb3NlZDsKfQoKY2xhc3MgQ29ubmVjdGlvblRocmVhZDIgOiBUaHJlYWQgewoJaW1wb3J0IHN0ZC5jb25jdXJyZW5jeTsKCXRoaXModm9pZCBmdW5jdGlvbihTb2NrZXQpIGhhbmRsZXIpIHsKCQl0aGlzLmhhbmRsZXIgPSBoYW5kbGVyOwoJCXN1cGVyKCZydW4pOwoJfQoKCXZvaWQgcnVuKCkgewoJCXRpZCA9IHRoaXNUaWQoKTsKCQlhdmFpbGFibGUgPSB0cnVlOwoJCXdoaWxlKHRydWUpCgkJcmVjZWl2ZSgKCQkJKC8qU29ja2V0Ki8gc2l6ZV90IHMpIHsKCQkJCWF2YWlsYWJsZSA9IGZhbHNlOwoJCQkJdHJ5IHsKCQkJCQloYW5kbGVyKGNhc3QoU29ja2V0KSBjYXN0KHZvaWQqKSBzKTsKCQkJCX0gY2F0Y2goVGhyb3dhYmxlIHQpIHt9CgkJCQlhdmFpbGFibGUgPSB0cnVlOwoJCQl9CgkJKTsKCX0KCglib29sIGF2YWlsYWJsZTsKCVRpZCB0aWQ7Cgl2b2lkIGZ1bmN0aW9uKFNvY2tldCkgaGFuZGxlcjsKfQoKLyoqCglUbyB1c2UgdGhpcyB0aGluZzoKCgl2b2lkIGhhbmRsZXIoU29ja2V0IHMpIHsgZG8gc29tZXRoaW5nLi4uIH0KCWF1dG8gbWFuYWdlciA9IG5ldyBMaXN0ZW5pbmdDb25uZWN0aW9uTWFuYWdlcigiMTI3LjAuMC4xIiwgODAsICZoYW5kbGVyKTsKCW1hbmFnZXIubGlzdGVuKCk7CgoJSSBzdWdnZXN0IHlvdSB1c2UgQnVmZmVyZWRJbnB1dFJhbmdlKGNvbm5lY3Rpb24pIHRvIGhhbmRsZSB0aGUgaW5wdXQuIEFzIGEgcGFja2V0Cgljb21lcyBpbiwgeW91IHdpbGwgZ2V0IGNvbnRyb2wuIFlvdSBjYW4ganVzdCBjb250aW51ZTsgdGhvdWdoIHRvIGZldGNoIG1vcmUuCgoKCUZJWE1FOiBzaG91bGQgSSBvZmZlciBhbiBldmVudCBiYXNlZCBhc3luYyB0aGluZyBsaWtlIG5ldG1hbiBkaWQgdG9vPyBZZWFoLCBwcm9iYWJseS4KKi8KY2xhc3MgTGlzdGVuaW5nQ29ubmVjdGlvbk1hbmFnZXIgewoJdm9pZCBsaXN0ZW4oKSB7CgkJdmVyc2lvbihjZ2lfbXVsdGlwbGVfY29ubmVjdGlvbnNfcGVyX3RocmVhZCkgewoJCQlpbXBvcnQgc3RkLmNvbmN1cnJlbmN5OwoJCQlpbXBvcnQgc3RkLnJhbmRvbTsKCQkJQ29ubmVjdGlvblRocmVhZDJbMTZdIHBvb2w7CgkJCWZvcmVhY2gocmVmIHA7IHBvb2wpIHsKCQkJCSBwID0gbmV3IENvbm5lY3Rpb25UaHJlYWQyKGhhbmRsZXIpOwoJCQkJIHAuc3RhcnQoKTsKCQkJfQoKCQkJd2hpbGUodHJ1ZSkgewoJCQkJYXV0byBjb25uZWN0aW9uID0gbGlzdGVuZXIuYWNjZXB0KCk7CgoJCQkJYm9vbCBoYW5kbGVkID0gZmFsc2U7CgkJCQlyZXRyeToKCQkJCWZvcmVhY2gocDsgcG9vbCkKCQkJCQlpZihwLmF2YWlsYWJsZSkgewoJCQkJCQloYW5kbGVkID0gdHJ1ZTsKCQkJCQkJc2VuZChwLnRpZCwgY2FzdChzaXplX3QpIGNhc3Qodm9pZCopIGNvbm5lY3Rpb24pOwoJCQkJCQlicmVhazsKCQkJCQl9CgoJCQkJLy8gbm9uZSBhdmFpbGFibGUgcmlnaHQgbm93LCBtYWtlIGl0IHdhaXQgYSBiaXQgdGhlbiB0cnkgYWdhaW4KCQkJCWlmKCFoYW5kbGVkKSB7CgkJCQkJVGhyZWFkLnNsZWVwKGR1ciEibXNlY3MiKDI1KSk7CgkJCQkJZ290byByZXRyeTsKCQkJCX0KCQkJfQoJCX0gZWxzZSB7CgkJCWZvcmVhY2goY29ubmVjdGlvbjsgdGhpcykKCQkJCWhhbmRsZXIoY29ubmVjdGlvbik7CgkJCQkKCQl9Cgl9CgoJdGhpcyhzdHJpbmcgaG9zdCwgdXNob3J0IHBvcnQsIHZvaWQgZnVuY3Rpb24oU29ja2V0KSBoYW5kbGVyKSB7CgkJdGhpcy5oYW5kbGVyID0gaGFuZGxlcjsKCQlsaXN0ZW5lciA9IG5ldyBUY3BTb2NrZXQoKTsKCQlsaXN0ZW5lci5zZXRPcHRpb24oU29ja2V0T3B0aW9uTGV2ZWwuU09DS0VULCBTb2NrZXRPcHRpb24uUkVVU0VBRERSLCB0cnVlKTsKCQlsaXN0ZW5lci5iaW5kKGhvc3QubGVuZ3RoID8gcGFyc2VBZGRyZXNzKGhvc3QsIHBvcnQpIDogbmV3IEludGVybmV0QWRkcmVzcyhwb3J0KSk7CgkJbGlzdGVuZXIubGlzdGVuKDEyOCk7Cgl9CgoJU29ja2V0IGxpc3RlbmVyOwoJdm9pZCBmdW5jdGlvbihTb2NrZXQpIGhhbmRsZXI7CgoJYm9vbCBydW5uaW5nOwoJdm9pZCBxdWl0KCkgewoJCXJ1bm5pbmcgPSBmYWxzZTsKCX0KCglpbnQgb3BBcHBseShzY29wZSBDTVQgZGcpIHsKCQlydW5uaW5nID0gdHJ1ZTsKCQlzaGFyZWQoaW50KSBsb29wQnJva2VuOwoKCQl3aGlsZSghbG9vcEJyb2tlbiAmJiBydW5uaW5nKSB7CgkJCWF1dG8gc24gPSBsaXN0ZW5lci5hY2NlcHQoKTsKCQkJdHJ5IHsKCQkJCXZlcnNpb24oY2dpX25vX3RocmVhZHMpIHsKCQkJCQkvLyBORVZFUiBVU0UgVEhJUwoJCQkJCS8vIGl0IGV4aXN0cyBvbmx5IGZvciBkZWJ1Z2dpbmcgYW5kIG90aGVyIHNwZWNpYWwgb2NjYXNpb25zCgoJCQkJCS8vIHRoZSB0aHJlYWQgbW9kZSBpcyBmYXN0ZXIgYW5kIGxlc3MgbGlrZWx5IHRvIHN0YWxsIHRoZSB3aG9sZQoJCQkJCS8vIHRoaW5nIHdoZW4gYSByZXF1ZXN0IGlzIHNsb3cKCQkJCQlkZyhzbik7CgkJCQl9IGVsc2UgewoJCQkJCS8qCgkJCQkJdmVyc2lvbihjZ2lfbXVsdGlwbGVfY29ubmVjdGlvbnNfcGVyX3RocmVhZCkgewoJCQkJCQlib29sIGZvdW5kT25lID0gZmFsc2U7CgkJCQkJCXRyeUFnYWluOgoJCQkJCQlmb3JlYWNoKHQ7IHBvb2wpCgkJCQkJCQlpZih0LnMgaXMgbnVsbCkgewoJCQkJCQkJCXQucyA9IHNuOwoJCQkJCQkJCWZvdW5kT25lID0gdHJ1ZTsKCQkJCQkJCQlicmVhazsKCQkJCQkJCX0KCQkJCQkJVGhyZWFkLnNsZWVwKGR1ciEibXNlY3MiKDEpKTsKCQkJCQkJaWYoIWZvdW5kT25lKQoJCQkJCQkJZ290byB0cnlBZ2FpbjsKCQkJCQl9IGVsc2UgewoJCQkJCSovCgkJCQkJCWF1dG8gdGhyZWFkID0gbmV3IENvbm5lY3Rpb25UaHJlYWQoc24sICZsb29wQnJva2VuLCBkZyk7CgkJCQkJCXRocmVhZC5zdGFydCgpOwoJCQkJCS8vfQoJCQkJfQoJCQkJLy8gbG9vcEJyb2tlbiA9IGRnKHNuKTsKCQkJfSBjYXRjaChFeGNlcHRpb24gZSkgewoJCQkJLy8gaWYgYSBjb25uZWN0aW9uIGdvZXMgd3JvbmcsIHdlIHdhbnQgdG8ganVzdCBzYXkgbm8sIGJ1dCB0cnkgdG8gY2Fycnkgb24gdW5sZXNzIGl0IGlzIGFuIEVycm9yIG9mIHNvbWUgc29ydCAoaW4gd2hpY2ggY2FzZSwgd2UnbGwgZGllLiBZb3UgbWlnaHQgd2FudCBhbiBleHRlcm5hbCBoZWxwZXIgcHJvZ3JhbSB0byByZXZpdmUgdGhlIHNlcnZlciB3aGVuIGl0IGRpZXMpCgkJCQlzbi5jbG9zZSgpOwoJCQl9CgkJfQoKCQlyZXR1cm4gbG9vcEJyb2tlbjsKCX0KfQoKLy8gaGVscGVyIGZ1bmN0aW9uIHRvIHNlbmQgYSBsb3QgdG8gYSBzb2NrZXQuIFNpbmNlIHRoaXMgYmxvY2tzIGZvciB0aGUgYnVmZmVyIChwb3NzaWJseSBzZXZlcmFsIHRpbWVzKSwgeW91IHNob3VsZCBwcm9iYWJseSBjYWxsIGl0IGluIGEgc2VwYXJhdGUgdGhyZWFkIG9yIHNvbWV0aGluZy4Kdm9pZCBzZW5kQWxsKFNvY2tldCBzLCBjb25zdCh2b2lkKVtdIGRhdGEsIHN0cmluZyBmaWxlID0gX19GSUxFX18sIHNpemVfdCBsaW5lID0gX19MSU5FX18pIHsKCWlmKGRhdGEubGVuZ3RoID09IDApIHJldHVybjsKCXB0cmRpZmZfdCBhbW91bnQ7CglkbyB7CgkJYW1vdW50ID0gcy5zZW5kKGRhdGEpOwoJCWlmKGFtb3VudCA9PSBTb2NrZXQuRVJST1IpCgkJCXRocm93IG5ldyBDb25uZWN0aW9uRXhjZXB0aW9uKHMsIGxhc3RTb2NrZXRFcnJvciwgZmlsZSwgbGluZSk7CgkJYXNzZXJ0KGFtb3VudCA+IDApOwoJCWRhdGEgPSBkYXRhW2Ftb3VudCAuLiAkXTsKCX0gd2hpbGUoZGF0YS5sZW5ndGgpOwp9CgpjbGFzcyBDb25uZWN0aW9uRXhjZXB0aW9uIDogRXhjZXB0aW9uIHsKCVNvY2tldCBzb2NrZXQ7Cgl0aGlzKFNvY2tldCBzLCBzdHJpbmcgbXNnLCBzdHJpbmcgZmlsZSA9IF9fRklMRV9fLCBzaXplX3QgbGluZSA9IF9fTElORV9fKSB7CgkJdGhpcy5zb2NrZXQgPSBzOwoJCXN1cGVyKG1zZywgZmlsZSwgbGluZSk7Cgl9Cn0KCmFsaWFzIGludCBkZWxlZ2F0ZShTb2NrZXQpIENNVDsKCmltcG9ydCBjb3JlLnRocmVhZDsKY2xhc3MgQ29ubmVjdGlvblRocmVhZCA6IFRocmVhZCB7Cgl0aGlzKFNvY2tldCBzLCBzaGFyZWQoaW50KSogYnJlYWtTaWduaWZpZXIsIENNVCBkZykgewoJCXRoaXMucyA9IHM7CgkgCXRoaXMuYnJlYWtTaWduaWZpZXIgPSBicmVha1NpZ25pZmllcjsKCQl0aGlzLmRnID0gZGc7CgkJc3VwZXIoJnJ1bkFsbCk7Cgl9CgoJdm9pZCBydW5BbGwoKSB7CgkJaWYocyAhaXMgbnVsbCkKCQkJcnVuKCk7CgkJLyoKCQl2ZXJzaW9uKGNnaV9tdWx0aXBsZV9jb25uZWN0aW9uc19wZXJfdGhyZWFkKSB7CgkJCXdoaWxlKDEpIHsKCQkJCXdoaWxlKHMgaXMgbnVsbCkKCQkJCQlzbGVlcChkdXIhIm1zZWNzIigxKSk7CgkJCQlydW4oKTsKCQkJfQoJCX0KCQkqLwoJfQoKCXZvaWQgcnVuKCkgewoJCXNjb3BlKGV4aXQpIHsKCQkJLy8gSSBkb24ndCB3YW50IHRvIGRvdWJsZSBjbG9zZSBpdCwgYW5kIGl0IGRvZXMgdGhpcyBvbiBjbG9zZSgpIGFjY29yZGluZyB0byBzb3VyY2UKCQkJLy8gbWlnaHQgYmUgZnJhZ2lsZSwgYnV0IG1laAoJCQlpZihzLmhhbmRsZSgpICE9IHNvY2tldF90LmluaXQpCgkJCQlzLmNsb3NlKCk7CgkJCXMgPSBudWxsOyAvLyBzbyB3ZSBrbm93IHRoaXMgdGhyZWFkIGlzIGNsZWFyCgkJfQoJCWlmKGF1dG8gcmVzdWx0ID0gZGcocykpIHsKCQkJKmJyZWFrU2lnbmlmaWVyID0gcmVzdWx0OwoJCX0KCX0KCglTb2NrZXQgczsKCXNoYXJlZChpbnQpKiBicmVha1NpZ25pZmllcjsKCUNNVCBkZzsKfQoKLyogRG9uZSB3aXRoIG5ldHdvcmsgaGVscGVyICovCgovKiBIZWxwZXJzIGZvciBkb2luZyB0ZW1wb3JhcnkgZmlsZXMuIFVzZWQgYm90aCBoZXJlIGFuZCBpbiB3ZWIuZCAqLwoKdmVyc2lvbihXaW5kb3dzKSB7CglpbXBvcnQgY29yZS5zeXMud2luZG93cy53aW5kb3dzOwoJZXh0ZXJuKFdpbmRvd3MpIERXT1JEIEdldFRlbXBQYXRoVyhEV09SRCwgTFBXU1RSKTsKCWFsaWFzIEdldFRlbXBQYXRoVyBHZXRUZW1wUGF0aDsKfQoKdmVyc2lvbihQb3NpeCkgewoJc3RhdGljIGltcG9ydCBsaW51eCA9IGNvcmUuc3lzLnBvc2l4LnVuaXN0ZDsKfQoKc3RyaW5nIGdldFRlbXBEaXJlY3RvcnkoKSB7CglzdHJpbmcgcGF0aDsKCXZlcnNpb24oV2luZG93cykgewoJCXdjaGFyWzEwMjRdIGJ1ZmZlcjsKCQlhdXRvIGxlbiA9IEdldFRlbXBQYXRoKDEwMjQsIGJ1ZmZlci5wdHIpOwoJCWlmKGxlbiA9PSAwKQoJCQl0aHJvdyBuZXcgRXhjZXB0aW9uKCJjb3VsZG4ndCBmaW5kIGEgdGVtcG9yYXJ5IHBhdGgiKTsKCgkJYXV0byBiID0gYnVmZmVyWzAgLi4gbGVuXTsKCgkJcGF0aCA9IHRvIXN0cmluZyhiKTsKCX0gZWxzZQoJCXBhdGggPSAiL3RtcC8iOwoKCXJldHVybiBwYXRoOwp9CgoKLy8gSSBsaWtlIHN0ZC5kYXRlLiBUaGVzZSBmdW5jdGlvbnMgaGVscCBrZWVwIG15IG9sZCBjb2RlIGFuZCBkYXRhIHdvcmtpbmcgd2l0aCBwaG9ib3MgY2hhbmdpbmcuCgpsb25nIHN5c1RpbWVUb0RUaW1lKGluIFN5c1RpbWUgc3lzVGltZSkgewogICAgcmV0dXJuIGNvbnZlcnQhKCJobnNlY3MiLCAibXNlY3MiKShzeXNUaW1lLnN0ZFRpbWUgLSA2MjEzNTU5NjgwMDAwMDAwMDBMKTsKfQoKbG9uZyBkYXRlVGltZVRvRFRpbWUoaW4gRGF0ZVRpbWUgZHQpIHsKCXJldHVybiBzeXNUaW1lVG9EVGltZShjYXN0KFN5c1RpbWUpIGR0KTsKfQoKbG9uZyBnZXRVdGNUaW1lKCkgeyAvLyByZW5hbWVkIHByaW1hcmlseSB0byBhdm9pZCBjb25mbGljdCB3aXRoIHN0ZC5kYXRlIGl0c2VsZgoJcmV0dXJuIHN5c1RpbWVUb0RUaW1lKENsb2NrLmN1cnJUaW1lKFVUQygpKSk7Cn0KCi8vIE5PVEU6IG5ldyBTaW1wbGVUaW1lWm9uZShtaW51dGVzKTsgY2FuIHBlcmhhcHMgd29yayB3aXRoIHRoZSBnZXRUaW1lem9uZU9mZnNldCgpIEpTIHRyaWNrClN5c1RpbWUgZFRpbWVUb1N5c1RpbWUobG9uZyBkVGltZSwgaW1tdXRhYmxlIFRpbWVab25lIHR6ID0gbnVsbCkgewoJaW1tdXRhYmxlIGhuc2VjcyA9IGNvbnZlcnQhKCJtc2VjcyIsICJobnNlY3MiKShkVGltZSkgKyA2MjEzNTU5NjgwMDAwMDAwMDBMOwoJcmV0dXJuIFN5c1RpbWUoaG5zZWNzLCB0eik7Cn0KCgoKLy8gdGhpcyBpcyBhIGhlbHBlciB0byByZWFkIEhUVFAgdHJhbnNmZXItZW5jb2Rpbmc6IGNodW5rZWQgcmVzcG9uc2VzCmltbXV0YWJsZSh1Ynl0ZVtdKSBkZWNodW5rKEJ1ZmZlcmVkSW5wdXRSYW5nZSBpcikgewoJaW1tdXRhYmxlKHVieXRlKVtdIHJldDsKCglhbm90aGVyX2NodW5rOgoJLy8gSWYgaGVyZSwgd2UgYXJlIGF0IHRoZSBiZWdpbm5pbmcgb2YgYSBjaHVuay4KCWF1dG8gYSA9IGlyLmZyb250KCk7CglpbnQgY2h1bmtTaXplOwoJaW50IGxvYyA9IGxvY2F0aW9uT2YoYSwgIlxyXG4iKTsKCXdoaWxlKGxvYyA9PSAtMSkgewoJCWlyLnBvcEZyb250KCk7CgkJYSA9IGlyLmZyb250KCk7CgkJbG9jID0gbG9jYXRpb25PZihhLCAiXHJcbiIpOwoJfQoKCXN0cmluZyBoZXg7CgloZXggPSAiIjsKCWZvcihpbnQgaSA9IDA7IGkgPCBsb2M7IGkrKykgewoJCWNoYXIgYyA9IGFbaV07CgkJaWYoYyA+PSAnQScgJiYgYyA8PSAnWicpCgkJCWMgKz0gMHgyMDsKCQlpZigoYyA+PSAnMCcgJiYgYyA8PSAnOScpIHx8IChjID49ICdhJyAmJiBjIDw9ICd6JykpIHsKCQkJaGV4IH49IGM7CgkJfSBlbHNlIHsKCQkJYnJlYWs7CgkJfQoJfQoKCWFzc2VydChoZXgubGVuZ3RoKTsKCglpbnQgcG93ZXIgPSAxOwoJaW50IHNpemUgPSAwOwoJZm9yZWFjaChjYzE7IHJldHJvKGhleCkpIHsKCQlkY2hhciBjYyA9IGNjMTsKCQlpZihjYyA+PSAnYScgJiYgY2MgPD0gJ3onKQoJCQljYyAtPSAweDIwOwoJCWludCB2YWwgPSAwOwoJCWlmKGNjID49ICcwJyAmJiBjYyA8PSAnOScpCgkJCXZhbCA9IGNjIC0gJzAnOwoJCWVsc2UKCQkJdmFsID0gY2MgLSAnQScgKyAxMDsKCgkJc2l6ZSArPSBwb3dlciAqIHZhbDsKCQlwb3dlciAqPSAxNjsKCX0KCgljaHVua1NpemUgPSBzaXplOwoJYXNzZXJ0KHNpemUgPj0gMCk7CgoJaWYobG9jICsgMiA+IGEubGVuZ3RoKSB7CgkJaXIucG9wRnJvbnQoMCwgYS5sZW5ndGggKyBsb2MgKyAyKTsKCQlhID0gaXIuZnJvbnQoKTsKCX0KCglhID0gaXIuY29uc3VtZShsb2MgKyAyKTsKCglpZihjaHVua1NpemUgPT0gMCkgeyAvLyB3ZSdyZSBkb25lIHdpdGggdGhlIHJlc3BvbnNlCgkJLy8gaWYgd2UgZ290IGhlcmUsIHdpbGwgY2hhbmdlIG11c3QgYmUgdHJ1ZS4uLi4KCQltb3JlX2Zvb3RlcnM6CgkJbG9jID0gbG9jYXRpb25PZihhLCAiXHJcbiIpOwoJCWlmKGxvYyA9PSAtMSkgewoJCQlpci5wb3BGcm9udCgpOwoJCQlhID0gaXIuZnJvbnQ7CgkJCWdvdG8gbW9yZV9mb290ZXJzOwoJCX0gZWxzZSB7CgkJCWFzc2VydChsb2MgPT0gMCk7CgkJCWlyLmNvbnN1bWUobG9jICsgMik7CgkJCWdvdG8gZmluaXNoOwoJCX0KCX0gZWxzZSB7CgkJLy8gaWYgd2UgZ290IGhlcmUsIHdpbGwgY2hhbmdlIG11c3QgYmUgdHJ1ZS4uLi4KCQlpZihhLmxlbmd0aCA8IGNodW5rU2l6ZSArIDIpIHsKCQkJaXIucG9wRnJvbnQoMCwgY2h1bmtTaXplICsgMik7CgkJCWEgPSBpci5mcm9udCgpOwoJCX0KCgkJcmV0IH49IChhWzAuLmNodW5rU2l6ZV0pOwoKCQlpZighKGEubGVuZ3RoID4gY2h1bmtTaXplICsgMikpIHsKCQkJaXIucG9wRnJvbnQoMCwgY2h1bmtTaXplICsgMik7CgkJCWEgPSBpci5mcm9udCgpOwoJCX0KCQlhc3NlcnQoYVtjaHVua1NpemVdID09IDEzKTsKCQlhc3NlcnQoYVtjaHVua1NpemUrMV0gPT0gMTApOwoJCWEgPSBpci5jb25zdW1lKGNodW5rU2l6ZSArIDIpOwoJCWNodW5rU2l6ZSA9IDA7CgkJZ290byBhbm90aGVyX2NodW5rOwoJfQoKCWZpbmlzaDoKCXJldHVybiByZXQ7Cn0KCi8vIEkgd2FudCB0byBiZSBhYmxlIHRvIGdldCBkYXRhIGZyb20gbXVsdGlwbGUgc291cmNlcyB0aGUgc2FtZSB3YXkuLi4KaW50ZXJmYWNlIEJ5Q2h1bmtSYW5nZSB7Cglib29sIGVtcHR5KCk7Cgl2b2lkIHBvcEZyb250KCk7Cgljb25zdCh1Ynl0ZSlbXSBmcm9udCgpOwp9CgpCeUNodW5rUmFuZ2UgYnlDaHVuayhjb25zdCh1Ynl0ZSlbXSBkYXRhKSB7CglyZXR1cm4gbmV3IGNsYXNzIEJ5Q2h1bmtSYW5nZSB7CgkJb3ZlcnJpZGUgYm9vbCBlbXB0eSgpIHsKCQkJcmV0dXJuICFkYXRhLmxlbmd0aDsKCQl9CgoJCW92ZXJyaWRlIHZvaWQgcG9wRnJvbnQoKSB7CgkJCWlmKGRhdGEubGVuZ3RoID4gNDA5NikKCQkJCWRhdGEgPSBkYXRhWzQwOTYgLi4gJF07CgkJCWVsc2UKCQkJCWRhdGEgPSBudWxsOwoJCX0KCgkJb3ZlcnJpZGUgY29uc3QodWJ5dGUpW10gZnJvbnQoKSB7CgkJCXJldHVybiBkYXRhWzAgLi4gJCA+IDQwOTYgPyA0MDk2IDogJF07CgkJfQoJfTsKfQoKQnlDaHVua1JhbmdlIGJ5Q2h1bmsoQnVmZmVyZWRJbnB1dFJhbmdlIGlyLCBzaXplX3QgYXRNb3N0KSB7Cgljb25zdCh1Ynl0ZSlbXSBmOwoKCWYgPSBpci5mcm9udDsKCWlmKGYubGVuZ3RoID4gYXRNb3N0KQoJCWYgPSBmWzAgLi4gYXRNb3N0XTsKCglyZXR1cm4gbmV3IGNsYXNzIEJ5Q2h1bmtSYW5nZSB7CgkJb3ZlcnJpZGUgYm9vbCBlbXB0eSgpIHsKCQkJcmV0dXJuIGF0TW9zdCA9PSAwOwoJCX0KCgkJb3ZlcnJpZGUgY29uc3QodWJ5dGUpW10gZnJvbnQoKSB7CgkJCXJldHVybiBmOwoJCX0KCgkJb3ZlcnJpZGUgdm9pZCBwb3BGcm9udCgpIHsKCQkJaXIuY29uc3VtZShmLmxlbmd0aCk7CgkJCWF0TW9zdCAtPSBmLmxlbmd0aDsKCQkJYXV0byBhID0gaXIuZnJvbnQoKTsKCgkJCWlmKGEubGVuZ3RoIDw9IGF0TW9zdCkgewoJCQkJZiA9IGE7CgkJCQlhdE1vc3QgLT0gYS5sZW5ndGg7CgkJCQlhID0gaXIuY29uc3VtZShhLmxlbmd0aCk7CgkJCQlpZihhdE1vc3QgIT0gMCkKCQkJCQlpci5wb3BGcm9udCgpOwoJCQkJaWYoZi5sZW5ndGggPT0gMCkgewoJCQkJCWYgPSBpci5mcm9udCgpOwoJCQkJfQoJCQl9IGVsc2UgewoJCQkJLy8gd2UgYWN0dWFsbHkgaGF2ZSAqbW9yZSogaGVyZSB0aGFuIHdlIG5lZWQuLi4uCgkJCQlmID0gYVswLi5hdE1vc3RdOwoJCQkJYXRNb3N0ID0gMDsKCQkJCWlyLmNvbnN1bWUoYXRNb3N0KTsKCQkJfQoJCX0KCX07Cn0KCnZlcnNpb24oY2dpX3dpdGhfd2Vic29ja2V0KSB7CgkvLyBodHRwOi8vdG9vbHMuaWV0Zi5vcmcvaHRtbC9yZmM2NDU1CgoJLyoqCgkJV0VCU09DS0VUIFNVUFBPUlQ6CgoJCUZ1bGwgZXhhbXBsZToKCQktLS0KCQkJaW1wb3J0IGFyc2QuY2dpOwoKCQkJdm9pZCB3ZWJzb2NrZXRFY2hvKENnaSBjZ2kpIHsKCQkJCWlmKGNnaS53ZWJzb2NrZXRSZXF1ZXN0ZWQoKSkgewoJCQkJCWlmKGNnaS5vcmlnaW4gIT0gImh0dHA6Ly9hcnNkbmV0Lm5ldCIpCgkJCQkJCXRocm93IG5ldyBFeGNlcHRpb24oImJhZCBvcmlnaW4iKTsKCQkJCQlhdXRvIHdlYnNvY2tldCA9IGNnaS5hY2NlcHRXZWJzb2NrZXQoKTsKCgkJCQkJd2Vic29ja2V0LnNlbmQoImhlbGxvIik7CgkJCQkJd2Vic29ja2V0LnNlbmQoIiB3b3JsZCEiKTsKCgkJCQkJYXV0byBtc2cgPSB3ZWJzb2NrZXQucmVjdigpOwoJCQkJCXdoaWxlKG1zZy5vcGNvZGUgIT0gV2ViU29ja2V0T3Bjb2RlLmNsb3NlKSB7CgkJCQkJCWlmKG1zZy5vcGNvZGUgPT0gV2ViU29ja2V0T3Bjb2RlLnRleHQpIHsKCQkJCQkJCXdlYnNvY2tldC5zZW5kKG1zZy50ZXh0RGF0YSk7CgkJCQkJCX0gZWxzZSBpZihtc2cub3Bjb2RlID09IFdlYlNvY2tldE9wY29kZS5iaW5hcnkpIHsKCQkJCQkJCXdlYnNvY2tldC5zZW5kKG1zZy5kYXRhKTsKCQkJCQkJfQoKCQkJCQkJbXNnID0gd2Vic29ja2V0LnJlY3YoKTsKCQkJCQl9CgoJCQkJCXdlYnNvY2tldC5jbG9zZSgpOwoJCQkJfSBlbHNlIGFzc2VydCgwLCAiaSB3YW50IGEgd2ViIHNvY2tldCEiKTsKCQkJfQoKCQkJbWl4aW4gR2VuZXJpY01haW4hd2Vic29ja2V0RWNobzsKCQktLS0KCSovCgoJY2xhc3MgV2ViU29ja2V0IHsKCQlDZ2kgY2dpOwoKCQlwcml2YXRlIHRoaXMoQ2dpIGNnaSkgewoJCQl0aGlzLmNnaSA9IGNnaTsKCQl9CgoJCS8vIHJldHVybnMgdHJ1ZSBpZiBkYXRhIGF2YWlsYWJsZSwgZmFsc2UgaWYgaXQgdGltZWQgb3V0CgkJYm9vbCByZWN2QXZhaWxhYmxlKER1cmF0aW9uIHRpbWVvdXQgPSBkdXIhIm1zZWNzIigwKSkgewoJCQlTb2NrZXQgc29ja2V0ID0gY2dpLmlkbG9sLnNvdXJjZTsKCgkJCWF1dG8gY2hlY2sgPSBuZXcgU29ja2V0U2V0KCk7CgkJCWNoZWNrLmFkZChzb2NrZXQpOwoKCQkJYXV0byBnb3QgPSBTb2NrZXQuc2VsZWN0KGNoZWNrLCBudWxsLCBudWxsLCB0aW1lb3V0KTsKCQkJaWYoZ290ID4gMCkKCQkJCXJldHVybiB0cnVlOwoJCQlyZXR1cm4gZmFsc2U7CgkJfQoKCQkvLyBub3RlOiB0aGlzIGJsb2NrcwoJCVdlYlNvY2tldE1lc3NhZ2UgcmVjdigpIHsKCQkJLy8gRklYTUU6IHNob3VsZCB3ZSBhdXRvbWF0aWNhbGx5IGhhbmRsZSBwaW5ncyBhbmQgcG9uZ3M/CgkJCWFzc2VydCghY2dpLmlkbG9sLmVtcHR5KCkpOwoJCQljZ2kuaWRsb2wucG9wRnJvbnQoMCk7CgoJCQlXZWJTb2NrZXRNZXNzYWdlIG1lc3NhZ2U7CgoJCQlhdXRvIGluZm8gPSBjZ2kuaWRsb2wuZnJvbnQoKTsKCgkJCS8vIEZJWE1FOiByZWFkIHNob3VsZCBwcm9sbHkgdGFrZSB0aGUgd2hvbGUgcmFuZ2Ugc28gaXQgY2FuIHJlcXVlc3QgbW9yZSBpZiBuZWVkZWQKCQkJLy8gcmVhZCBzaG91bGQgYWxzbyBnbyBhaGVhZCBhbmQgY29uc3VtZSB0aGUgcmFuZ2UKCQkJbWVzc2FnZSA9IFdlYlNvY2tldE1lc3NhZ2UucmVhZChpbmZvKTsKCgkJCWNnaS5pZGxvbC5jb25zdW1lKGluZm8ubGVuZ3RoKTsKCgkJCXJldHVybiBtZXNzYWdlOwoJCX0KCgkJdm9pZCBzZW5kKGluIGNoYXJbXSB0ZXh0KSB7CgkJCS8vIEkgY2FzdCBhd2F5IGNvbnN0IGhlcmUgYmVjYXVzZSBJIGtub3cgdGhpcyBtc2cgaXMgcHJpdmF0ZSBhbmQgaXQgZG9lc24ndCB3cml0ZQoJCQkvLyB0byB0aGF0IGJ1ZmZlciB1bmxlc3MgbWFza2luZyBpcyBzZXQuLi4gd2hpY2ggaXQgaXNuJ3QsIHNvIHdlJ3JlIG9rLgoJCQlhdXRvIG1zZyA9IFdlYlNvY2tldE1lc3NhZ2Uuc2ltcGxlTWVzc2FnZShXZWJTb2NrZXRPcGNvZGUudGV4dCwgY2FzdCh2b2lkW10pIHRleHQpOwoJCQltc2cuc2VuZChjZ2kpOwoJCX0KCgkJdm9pZCBzZW5kKGluIHVieXRlW10gYmluYXJ5KSB7CgkJCS8vIEkgY2FzdCBhd2F5IGNvbnN0IGhlcmUgYmVjYXVzZSBJIGtub3cgdGhpcyBtc2cgaXMgcHJpdmF0ZSBhbmQgaXQgZG9lc24ndCB3cml0ZQoJCQkvLyB0byB0aGF0IGJ1ZmZlciB1bmxlc3MgbWFza2luZyBpcyBzZXQuLi4gd2hpY2ggaXQgaXNuJ3QsIHNvIHdlJ3JlIG9rLgoJCQlhdXRvIG1zZyA9IFdlYlNvY2tldE1lc3NhZ2Uuc2ltcGxlTWVzc2FnZShXZWJTb2NrZXRPcGNvZGUuYmluYXJ5LCBjYXN0KHZvaWRbXSkgYmluYXJ5KTsKCQkJbXNnLnNlbmQoY2dpKTsKCQl9CgoJCXZvaWQgY2xvc2UoKSB7CgkJCWF1dG8gbXNnID0gV2ViU29ja2V0TWVzc2FnZS5zaW1wbGVNZXNzYWdlKFdlYlNvY2tldE9wY29kZS5jbG9zZSwgbnVsbCk7CgkJCW1zZy5zZW5kKGNnaSk7CgkJfQoKCQl2b2lkIHBpbmcoKSB7CgkJCWF1dG8gbXNnID0gV2ViU29ja2V0TWVzc2FnZS5zaW1wbGVNZXNzYWdlKFdlYlNvY2tldE9wY29kZS5waW5nLCBudWxsKTsKCQkJbXNnLnNlbmQoY2dpKTsKCQl9CgoJCXZvaWQgcG9uZygpIHsKCQkJYXV0byBtc2cgPSBXZWJTb2NrZXRNZXNzYWdlLnNpbXBsZU1lc3NhZ2UoV2ViU29ja2V0T3Bjb2RlLnBvbmcsIG51bGwpOwoJCQltc2cuc2VuZChjZ2kpOwoJCX0KCX0KCglib29sIHdlYnNvY2tldFJlcXVlc3RlZChDZ2kgY2dpKSB7CgkJcmV0dXJuCgkJCSJzZWMtd2Vic29ja2V0LWtleSIgaW4gY2dpLnJlcXVlc3RIZWFkZXJzCgkJCSYmCgkJCSJjb25uZWN0aW9uIiBpbiBjZ2kucmVxdWVzdEhlYWRlcnMgJiYKCQkJCWNnaS5yZXF1ZXN0SGVhZGVyc1siY29ubmVjdGlvbiJdLnRvTG93ZXIoKS5pbmRleE9mKCJ1cGdyYWRlIikgIT0gLTEKCQkJJiYKCQkJInVwZ3JhZGUiIGluIGNnaS5yZXF1ZXN0SGVhZGVycyAmJgoJCQkJY2dpLnJlcXVlc3RIZWFkZXJzWyJ1cGdyYWRlIl0udG9Mb3dlcigpID09ICJ3ZWJzb2NrZXQiCgkJCTsKCX0KCglXZWJTb2NrZXQgYWNjZXB0V2Vic29ja2V0KENnaSBjZ2kpIHsKCQlhc3NlcnQoIWNnaS5jbG9zZWQpOwoJCWFzc2VydCghY2dpLm91dHB1dHRlZFJlc3BvbnNlRGF0YSk7CgkJY2dpLnNldFJlc3BvbnNlU3RhdHVzKCIxMDEgV2ViIFNvY2tldCBQcm90b2NvbCBIYW5kc2hha2UiKTsKCQljZ2kuaGVhZGVyKCJVcGdyYWRlOiBXZWJTb2NrZXQiKTsKCQljZ2kuaGVhZGVyKCJDb25uZWN0aW9uOiB1cGdyYWRlIik7CgoJCXN0cmluZyBrZXkgPSBjZ2kucmVxdWVzdEhlYWRlcnNbInNlYy13ZWJzb2NrZXQta2V5Il07CgkJa2V5IH49ICIyNThFQUZBNS1FOTE0LTQ3REEtOTVDQS1DNUFCMERDODVCMTEiOwoKCQlpbXBvcnQgYXJzZC5zaGE7CgkJYXV0byBhY2NlcHQgPSBCYXNlNjQuZW5jb2RlKFNIQTEoa2V5KSk7CgoJCWNnaS5oZWFkZXIoKCJTZWMtV2ViU29ja2V0LUFjY2VwdDogIiB+IGFjY2VwdCkuaWR1cCk7CgoJCWNnaS53ZWJzb2NrZXRNb2RlID0gdHJ1ZTsKCQljZ2kud3JpdGUoIiIpOwoKCQljZ2kuZmx1c2goKTsKCgkJcmV0dXJuIG5ldyBXZWJTb2NrZXQoY2dpKTsKCX0KCgkvLyBGSVhNRTogaW1wbGVtZW50IHdlYnNvY2tldCBleHRlbnNpb24gZnJhbWVzCgkvLyBnZXQgd2Vic29ja2V0IHRvIHdvcmsgb24gb3RoZXIgbW9kZXMsIG5vdCBqdXN0IGVtYmVkZGVkX2h0dHBkCgoJZW51bSBXZWJTb2NrZXRPcGNvZGUgOiB1Ynl0ZSB7CgkJdGV4dCA9IDEsCgkJYmluYXJ5ID0gMiwKCQkvLyAzLCA0LCA1LCA2LCA3IFJFU0VSVkVECgkJY2xvc2UgPSA4LAoJCXBpbmcgPSA5LAoJCXBvbmcgPSAxMCwKCQkvLyAxMSwxMiwxMywxNCwxNSBSRVNFUlZFRAoJfQoKCXN0cnVjdCBXZWJTb2NrZXRNZXNzYWdlIHsKCQlib29sIGZpbjsKCQlib29sIHJzdjE7CgkJYm9vbCByc3YyOwoJCWJvb2wgcnN2MzsKCQlXZWJTb2NrZXRPcGNvZGUgb3Bjb2RlOyAvLyA0IGJpdHMKCQlib29sIG1hc2tlZDsKCQl1Ynl0ZSBsZW5ndGhJbmRpY2F0b3I7IC8vIGRvbid0IHNldCB0aGlzIHdoZW4gYnVpbGRpbmcgb25lIHRvIHNlbmQKCQl1bG9uZyByZWFsTGVuZ3RoOyAvLyBkb24ndCB1c2Ugd2hlbiBzZW5kaW5nCgkJdWJ5dGVbNF0gbWFza2luZ0tleTsgLy8gZG9uJ3Qgc2V0IHRoaXMgd2hlbiBzZW5kaW5nCgkJdWJ5dGVbXSBkYXRhOwoKCQlzdGF0aWMgV2ViU29ja2V0TWVzc2FnZSBzaW1wbGVNZXNzYWdlKFdlYlNvY2tldE9wY29kZSBvcGNvZGUsIHZvaWRbXSBkYXRhKSB7CgkJCVdlYlNvY2tldE1lc3NhZ2UgbXNnOwoJCQltc2cuZmluID0gdHJ1ZTsKCQkJbXNnLm9wY29kZSA9IG9wY29kZTsKCQkJbXNnLmRhdGEgPSBjYXN0KHVieXRlW10pIGRhdGE7CgoJCQlyZXR1cm4gbXNnOwoJCX0KCgkJcHJpdmF0ZSB2b2lkIHNlbmQoQ2dpIGNnaSkgewoJCQl1Ynl0ZVs2NF0gaGVhZGVyU2NyYXRjaDsKCQkJaW50IGhlYWRlclNjcmF0Y2hQb3MgPSAwOwoKCQkJcmVhbExlbmd0aCA9IGRhdGEubGVuZ3RoOwoKCQkJewoJCQkJdWJ5dGUgYjE7CgkJCQliMSB8PSBjYXN0KHVieXRlKSBvcGNvZGU7CgkJCQliMSB8PSByc3YzID8gKDEgPDwgNCkgOiAwOwoJCQkJYjEgfD0gcnN2MiA/ICgxIDw8IDUpIDogMDsKCQkJCWIxIHw9IHJzdjEgPyAoMSA8PCA2KSA6IDA7CgkJCQliMSB8PSBmaW4gID8gKDEgPDwgNykgOiAwOwoKCQkJCWhlYWRlclNjcmF0Y2hbMF0gPSBiMTsKCQkJCWhlYWRlclNjcmF0Y2hQb3MrKzsKCQkJfQoKCQkJewoJCQkJaGVhZGVyU2NyYXRjaFBvcysrOyAvLyB3ZSdsbCBzZXQgaGVhZGVyWzFdIGF0IHRoZSBlbmQgb2YgdGhpcwoJCQkJYXV0byBybGMgPSByZWFsTGVuZ3RoOwoJCQkJdWJ5dGUgYjI7CgkJCQliMiB8PSBtYXNrZWQgPyAoMSA8PCA3KSA6IDA7CgoJCQkJYXNzZXJ0KGhlYWRlclNjcmF0Y2hQb3MgPT0gMik7CgoJCQkJaWYocmVhbExlbmd0aCA+IDY1NTM1KSB7CgkJCQkJLy8gdXNlIDY0IGJpdCBsZW5ndGgKCQkJCQliMiB8PSAweDdmOwoKCQkJCQkvLyBGSVhNRTogZG91YmxlIGNoZWNrIGVuZGluYW5lc3MKCQkJCQlmb3JlYWNoKGk7IDAgLi4gOCkgewoJCQkJCQloZWFkZXJTY3JhdGNoWzIgKyA3IC0gaV0gPSBybGMgJiAweDBmZjsKCQkJCQkJcmxjID4+Pj0gODsKCQkJCQl9CgoJCQkJCWhlYWRlclNjcmF0Y2hQb3MgKz0gODsKCQkJCX0gZWxzZSBpZihyZWFsTGVuZ3RoID4gMTI3KSB7CgkJCQkJLy8gdXNlIDE2IGJpdCBsZW5ndGgKCQkJCQliMiB8PSAweDdlOwoKCQkJCQkvLyBGSVhNRTogZG91YmxlIGNoZWNrIGVuZGluYW5lc3MKCQkJCQlmb3JlYWNoKGk7IDAgLi4gMikgewoJCQkJCQloZWFkZXJTY3JhdGNoWzIgKyAxIC0gaV0gPSBybGMgJiAweDBmZjsKCQkJCQkJcmxjID4+Pj0gODsKCQkJCQl9CgoJCQkJCWhlYWRlclNjcmF0Y2hQb3MgKz0gMjsKCQkJCX0gZWxzZSB7CgkJCQkJLy8gdXNlIDcgYml0IGxlbmd0aAoJCQkJCWIyIHw9IHJlYWxMZW5ndGggJiAwYl8wMTExXzExMTE7CgkJCQl9CgoJCQkJaGVhZGVyU2NyYXRjaFsxXSA9IGIyOwoJCQl9CgoJCQlhc3NlcnQoIW1hc2tlZCwgIm1hc2tpbmcga2V5IG5vdCBwcm9wZXJseSBpbXBsZW1lbnRlZCIpOwoJCQlpZihtYXNrZWQpIHsKCQkJCS8vIEZJWE1FOiByYW5kb21pemUgdGhpcwoJCQkJaGVhZGVyU2NyYXRjaFtoZWFkZXJTY3JhdGNoUG9zIC4uIGhlYWRlclNjcmF0Y2hQb3MgKyA0XSA9IG1hc2tpbmdLZXlbXTsKCQkJCWhlYWRlclNjcmF0Y2hQb3MgKz0gNDsKCgkJCQkvLyB3ZSdsbCBqdXN0IG1hc2sgaXQgaW4gcGxhY2UuLi4KCQkJCWludCBrZXlJZHggPSAwOwoJCQkJZm9yZWFjaChpOyAwIC4uIGRhdGEubGVuZ3RoKSB7CgkJCQkJZGF0YVtpXSA9IGRhdGFbaV0gXiBtYXNraW5nS2V5W2tleUlkeF07CgkJCQkJaWYoa2V5SWR4ID09IDMpCgkJCQkJCWtleUlkeCA9IDA7CgkJCQkJZWxzZQoJCQkJCQlrZXlJZHgrKzsKCQkJCX0KCQkJfQoKCQkJLy93cml0ZWxuKCJTRU5ESU5HICIsIGhlYWRlclNjcmF0Y2hbMCAuLiBoZWFkZXJTY3JhdGNoUG9zXSwgZGF0YSk7CgkJCWNnaS53cml0ZShoZWFkZXJTY3JhdGNoWzAgLi4gaGVhZGVyU2NyYXRjaFBvc10pOwoJCQljZ2kud3JpdGUoZGF0YSk7CgkJCWNnaS5mbHVzaCgpOwoJCX0KCgkJc3RhdGljIFdlYlNvY2tldE1lc3NhZ2UgcmVhZCh1Ynl0ZVtdIGQpIHsKCQkJV2ViU29ja2V0TWVzc2FnZSBtc2c7CgkJCWFzc2VydChkLmxlbmd0aCA+PSAyKTsKCgkJCXVieXRlIGIgPSBkWzBdOwoKCQkJbXNnLm9wY29kZSA9IGNhc3QoV2ViU29ja2V0T3Bjb2RlKSAoYiAmIDB4MGYpOwoJCQliID4+PSA0OwoJCQltc2cucnN2MyA9IGIgJiAweDAxOwoJCQliID4+PSAxOwoJCQltc2cucnN2MiA9IGIgJiAweDAxOwoJCQliID4+PSAxOwoJCQltc2cucnN2MSA9IGIgJiAweDAxOwoJCQliID4+PSAxOwoJCQltc2cuZmluID0gYiAmIDB4MDE7CgoJCQliID0gZFsxXTsKCQkJbXNnLm1hc2tlZCA9IChiICYgMGIxMDAwXzAwMDApID8gdHJ1ZSA6IGZhbHNlOwoJCQltc2cubGVuZ3RoSW5kaWNhdG9yID0gYiAmIDBiMDExMV8xMTExOwoKCQkJZCA9IGRbMiAuLiAkXTsKCgkJCWlmKG1zZy5sZW5ndGhJbmRpY2F0b3IgPT0gMHg3ZSkgewoJCQkJLy8gMTYgYml0IGxlbmd0aAoJCQkJbXNnLnJlYWxMZW5ndGggPSAwOwoKCQkJCWZvcmVhY2goaTsgMCAuLiAyKSB7CgkJCQkJbXNnLnJlYWxMZW5ndGggfD0gZFswXSA8PCAoKDEtaSkgKiA4KTsKCQkJCQlkID0gZFsxIC4uICRdOwoJCQkJfQoJCQl9IGVsc2UgaWYobXNnLmxlbmd0aEluZGljYXRvciA9PSAweDdmKSB7CgkJCQkvLyA2NCBiaXQgbGVuZ3RoCgkJCQltc2cucmVhbExlbmd0aCA9IDA7CgoJCQkJZm9yZWFjaChpOyAwIC4uIDgpIHsKCQkJCQltc2cucmVhbExlbmd0aCB8PSBkWzBdIDw8ICgoNy1pKSAqIDgpOwoJCQkJCWQgPSBkWzEgLi4gJF07CgkJCQl9CgkJCX0gZWxzZSB7CgkJCQkvLyA3IGJpdCBsZW5ndGgKCQkJCW1zZy5yZWFsTGVuZ3RoID0gbXNnLmxlbmd0aEluZGljYXRvcjsKCQkJfQoKCQkJaWYobXNnLm1hc2tlZCkgewoJCQkJbXNnLm1hc2tpbmdLZXkgPSBkWzAgLi4gNF07CgkJCQlkID0gZFs0IC4uICRdOwoJCQl9CgoJCQltc2cuZGF0YSA9IGRbMCAuLiAkXTsKCgkJCWlmKG1zZy5tYXNrZWQpIHsKCQkJCS8vIGxldCdzIGp1c3QgdW5tYXNrIGl0IG5vdwoJCQkJaW50IGtleUlkeCA9IDA7CgkJCQlmb3JlYWNoKGk7IDAgLi4gbXNnLmRhdGEubGVuZ3RoKSB7CgkJCQkJbXNnLmRhdGFbaV0gPSBtc2cuZGF0YVtpXSBeIG1zZy5tYXNraW5nS2V5W2tleUlkeF07CgkJCQkJaWYoa2V5SWR4ID09IDMpCgkJCQkJCWtleUlkeCA9IDA7CgkJCQkJZWxzZQoJCQkJCQlrZXlJZHgrKzsKCQkJCX0KCQkJfQoKCQkJcmV0dXJuIG1zZzsKCQl9CgoJCWNoYXJbXSB0ZXh0RGF0YSgpIHsKCQkJcmV0dXJuIGNhc3QoY2hhcltdKSBkYXRhOwoJCX0KCX0KCn0KCgp2ZXJzaW9uKFdpbmRvd3MpCnsKICAgIHZlcnNpb24oQ1J1bnRpbWVfRGlnaXRhbE1hcnMpCiAgICB7CiAgICAgICAgZXh0ZXJuKEMpIGludCBzZXRtb2RlKGludCwgaW50KSBub3Rocm93IEBub2djOwogICAgfQogICAgZWxzZSB2ZXJzaW9uKENSdW50aW1lX01pY3Jvc29mdCkKICAgIHsKICAgICAgICBleHRlcm4oQykgaW50IF9zZXRtb2RlKGludCwgaW50KSBub3Rocm93IEBub2djOwogICAgICAgIGFsaWFzIHNldG1vZGUgPSBfc2V0bW9kZTsKICAgIH0KICAgIGVsc2Ugc3RhdGljIGFzc2VydCgwKTsKfQoKLyoKQ29weXJpZ2h0OiBBZGFtIEQuIFJ1cHBlLCAyMDA4IC0gMjAxNgpMaWNlbnNlOiAgIDxhIGhyZWY9Imh0dHA6Ly93d3cuYm9vc3Qub3JnL0xJQ0VOU0VfMV8wLnR4dCI+Qm9vc3QgTGljZW5zZSAxLjA8L2E+LgpBdXRob3JzOiBBZGFtIEQuIFJ1cHBlCgoJQ29weXJpZ2h0IEFkYW0gRC4gUnVwcGUgMjAwOCAtIDIwMTYuCkRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBCb29zdCBTb2Z0d2FyZSBMaWNlbnNlLCBWZXJzaW9uIDEuMC4KICAgKFNlZSBhY2NvbXBhbnlpbmcgZmlsZSBMSUNFTlNFXzFfMC50eHQgb3IgY29weSBhdAoJaHR0cDovL3d3dy5ib29zdC5vcmcvTElDRU5TRV8xXzAudHh0KQoqLw==\"") packr.PackJSONBytes("./images", "d/fx.d", "\"aW1wb3J0IHN0ZC5qc29uOwoKbG9uZyBleGVjdXRlRngoSlNPTlZhbHVlIGlucHV0KQp7CiAgICByZXR1cm4gaW5wdXRbImEiXS5pbnRlZ2VyICsgaW5wdXRbImIiXS5pbnRlZ2VyOwp9\"") - packr.PackJSONBytes("./images", "go/Dockerfile", "\"RlJPTSBtZXRydWUvZngtZ28tYmFzZTpsYXRlc3QKCkNPUFkgLiAvZ28vc3JjL2dpdGh1Yi5jb20vbWV0cnVlL2Z4CldPUktESVIgL2dvL3NyYy9naXRodWIuY29tL21ldHJ1ZS9meAoKUlVOIGdvIGJ1aWxkIC1sZGZsYWdzICItdyAtcyIgLW8gZnggZnguZ28gYXBwLmdvCgpFWFBPU0UgMzAwMAoKQ01EIFsiLi9meCJdCg==\"") + packr.PackJSONBytes("./images", "go/Dockerfile", "\"RlJPTSBnb2xhbmc6bGF0ZXN0CgpDT1BZIC4gL2dvL3NyYy9naXRodWIuY29tL21ldHJ1ZS9meApXT1JLRElSIC9nby9zcmMvZ2l0aHViLmNvbS9tZXRydWUvZngKCiMgZGVwZW5kZW5jeSBtYW5hZ2VtZW50ClJVTiBnbyBnZXQgZ2l0aHViLmNvbS9naW4tZ29uaWMvZ2luCgpSVU4gZ28gYnVpbGQgLWxkZmxhZ3MgIi13IC1zIiAtbyBmeCBmeC5nbyBhcHAuZ28KCkVYUE9TRSAzMDAwCgpDTUQgWyIuL2Z4Il0K\"") packr.PackJSONBytes("./images", "go/app.go", "\"cGFja2FnZSBtYWluCgppbXBvcnQgKAoJImdpdGh1Yi5jb20vZ2luLWdvbmljL2dpbiIKKQoKZnVuYyBtYWluKCkgewoJciA6PSBnaW4uRGVmYXVsdCgpCglyLkdFVCgiLyIsIGZ4KQoJci5QT1NUKCIvIiwgZngpCglpZiBlcnIgOj0gci5SdW4oIjozMDAwIik7IGVyciAhPSBuaWwgewoJCXBhbmljKGVycikKCX0KfQo=\"") packr.PackJSONBytes("./images", "go/fx.go", "\"cGFja2FnZSBtYWluCgppbXBvcnQgImdpdGh1Yi5jb20vZ2luLWdvbmljL2dpbiIKCmZ1bmMgZngoY3R4ICpnaW4uQ29udGV4dCkgewoJY3R4LkpTT04oMjAwLCBnaW4uSHsKCQkibWVzc2FnZSI6ICJoZWxsbyB3b3JsZCIsCgl9KQp9Cg==\"") packr.PackJSONBytes("./images", "java/Dockerfile", "\"RlJPTSBtZXRydWUvZngtamF2YS1iYXNlCgojIEFkZGluZyBzb3VyY2UsIGNvbXBpbGUgYW5kIHBhY2thZ2UgaW50byBhIGZhdCBqYXIKQUREIHNyYyAvY29kZS9zcmMKUlVOIFsibXZuIiwgInBhY2thZ2UiXQoKRVhQT1NFIDMwMDAKQ01EIFsiL3Vzci9saWIvanZtL2phdmEtOC1vcGVuamRrLWFtZDY0L2Jpbi9qYXZhIiwgIi1qYXIiLCAidGFyZ2V0L2Z4LWFwcC1qYXZhLTAuMS4wLmphciJdCg==\"") diff --git a/packer/docker_packer_test.go b/packer/docker_packer_test.go index 47c4b43b..2ce58d34 100644 --- a/packer/docker_packer_test.go +++ b/packer/docker_packer_test.go @@ -20,7 +20,7 @@ module.exports = ({a, b}) => { return a + b } ` - fn := types.ServiceFunctionSource{ + fn := types.Func{ Language: "node", Source: mockSource, } diff --git a/packer/images/go/Dockerfile b/packer/images/go/Dockerfile index 922f15ea..37becc25 100644 --- a/packer/images/go/Dockerfile +++ b/packer/images/go/Dockerfile @@ -1,8 +1,11 @@ -FROM metrue/fx-go-base:latest +FROM golang:latest COPY . /go/src/github.com/metrue/fx WORKDIR /go/src/github.com/metrue/fx +# dependency management +RUN go get github.com/gin-gonic/gin + RUN go build -ldflags "-w -s" -o fx fx.go app.go EXPOSE 3000 diff --git a/packer/packer_test.go b/packer/packer_test.go index cd48cdb7..eda2bac4 100644 --- a/packer/packer_test.go +++ b/packer/packer_test.go @@ -1,7 +1,6 @@ package packer import ( - "encoding/base64" "testing" "github.com/metrue/fx/types" @@ -13,7 +12,7 @@ module.exports = ({a, b}) => { return a + b } ` - fn := types.ServiceFunctionSource{ + fn := types.Func{ Language: "node", Source: mockSource, } @@ -70,16 +69,12 @@ public class Fx { } } ` - fn := types.ServiceFunctionSource{ + fn := types.Func{ Language: "java", Source: mockSource, } - tree, err := PackIntoK8SConfigMapFile(fn.Language, fn.Source) + _, err := PackIntoK8SConfigMapFile(fn) if err != nil { t.Fatal(err) } - body := base64.StdEncoding.EncodeToString([]byte(mockSource)) - if tree["src/main/java/fx/Fx.java"] != body { - t.Fatalf("should get %s but got %s", body, tree["src/main/java/fx/app.java"]) - } } diff --git a/provision/provision.go b/provision/provision.go index 09c29659..24ef39a2 100644 --- a/provision/provision.go +++ b/provision/provision.go @@ -2,11 +2,10 @@ package provision import ( "fmt" - "strings" + "os" "sync" "github.com/apex/log" - "github.com/metrue/fx/config" "github.com/metrue/fx/constants" "github.com/metrue/fx/pkg/command" ssh "github.com/metrue/go-ssh-client" @@ -20,25 +19,82 @@ type Provisioner interface { // Provisionor provision-or type Provisionor struct { sshClient ssh.Client - - host config.Host + host string } -// New new provision -func New(host config.Host) *Provisionor { - p := &Provisionor{host: host} - if host.IsRemote() { - p.sshClient = ssh.New(host.Host). - WithUser(host.User). - WithPassword(host.Password) +func isLocal(host string) bool { + if host == "" { + return false + } + return host == "127.0.0.1" || host == "localhost" || host == "0.0.0.0" +} + +// NewWithHost create a provisionor with host, user, and password +func NewWithHost(host string, user string, password string) *Provisionor { + p := &Provisionor{ + host: host, + } + if !isLocal(host) { + p.sshClient = ssh.New(host). + WithUser(user). + WithPassword(password) } return p } +// IsFxAgentRunning check if fx-agent is running on host +func (p *Provisionor) IsFxAgentRunning() bool { + script := fmt.Sprintf("docker inspect %s", constants.AgentContainerName) + var cmd *command.Command + if !isLocal(p.host) { + cmd = command.New("inspect fx-agent", script, command.NewRemoteRunner(p.sshClient)) + } else { + cmd = command.New("inspect fx-agent", script, command.NewLocalRunner()) + } + output, err := cmd.Exec() + if os.Getenv("DEBUG") != "" { + log.Infof(string(output)) + } + if err != nil { + return false + } + return true +} + +// StartFxAgent start fx agent +func (p *Provisionor) StartFxAgent() error { + script := fmt.Sprintf("docker run -d --name=%s --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:%s:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock", constants.AgentContainerName, constants.AgentPort) + var cmd *command.Command + if !isLocal(p.host) { + cmd = command.New("start fx-agent", script, command.NewRemoteRunner(p.sshClient)) + } else { + cmd = command.New("start fx-agent", script, command.NewLocalRunner()) + } + if output, err := cmd.Exec(); err != nil { + log.Info(string(output)) + return err + } + return nil +} + +// StopFxAgent stop fx agent +func (p *Provisionor) StopFxAgent() error { + script := fmt.Sprintf("docker stop %s", constants.AgentContainerName) + var cmd *command.Command + if !isLocal(p.host) { + cmd = command.New("stop fx agent", script, command.NewRemoteRunner(p.sshClient)) + } else { + cmd = command.New("stop fx agent", script, command.NewLocalRunner()) + } + if output, err := cmd.Exec(); err != nil { + log.Infof(string(output)) + return err + } + return nil +} + // Start start provision progress func (p *Provisionor) Start() error { - startFxAgent := fmt.Sprintf("docker run -d --name=%s --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:%s:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock", constants.AgentContainerName, constants.AgentPort) - stopFxAgent := fmt.Sprintf("docker stop %s", constants.AgentContainerName) scripts := map[string]string{ "pull java Docker base image": "docker pull metrue/fx-java-base", "pull julia Docker base image": "docker pull metrue/fx-julia-base", @@ -48,35 +104,12 @@ func (p *Provisionor) Start() error { "pull go Docker base image": "docker pull metrue/fx-go-base", } - agentStartupCmds := []*command.Command{} - if p.host.IsRemote() { - agentStartupCmds = append(agentStartupCmds, - command.New("stop current fx agent", stopFxAgent, command.NewRemoteRunner(p.sshClient)), - command.New("start fx agent", startFxAgent, command.NewRemoteRunner(p.sshClient)), - ) - } else { - agentStartupCmds = append(agentStartupCmds, - command.New("stop current fx agent", stopFxAgent, command.NewLocalRunner()), - command.New("start fx agent", startFxAgent, command.NewLocalRunner()), - ) - } - for _, cmd := range agentStartupCmds { - if output, err := cmd.Exec(); err != nil { - if strings.Contains(string(output), "No such container: fx-agent") { - // Skip stop a fx-agent error when there is not agent running - } else { - log.Fatalf("Provision:%s: %s, %s", cmd.Name, err, output) - return err - } - } - } - var wg sync.WaitGroup for n, s := range scripts { wg.Add(1) go func(name, script string) { var cmd *command.Command - if p.host.IsRemote() { + if !isLocal(p.host) { cmd = command.New(name, script, command.NewRemoteRunner(p.sshClient)) } else { cmd = command.New(name, script, command.NewLocalRunner()) diff --git a/provision/provision_test.go b/provision/provision_test.go index 5eb263a1..095c22fd 100644 --- a/provision/provision_test.go +++ b/provision/provision_test.go @@ -2,14 +2,35 @@ package provision import ( "testing" - - "github.com/metrue/fx/config" + "time" ) -func TestStart(t *testing.T) { - host := config.Host{Host: "127.0.0.1"} - provisionor := New(host) +func TestProvisionWorkflow(t *testing.T) { + provisionor := NewWithHost("127.0.0.1", "", "") + + _ = provisionor.StopFxAgent() + // TODO wait too long here to make test pass + time.Sleep(40 * time.Second) + + running := provisionor.IsFxAgentRunning() + if running { + t.Fatalf("fx-agent should not be running") + } + + if err := provisionor.StartFxAgent(); err != nil { + t.Fatal(err) + } + + running = provisionor.IsFxAgentRunning() + if !running { + t.Fatalf("fx-agent should be running") + } + if err := provisionor.Start(); err != nil { t.Fatal(err) } + + if err := provisionor.StopFxAgent(); err != nil { + t.Fatal(err) + } } diff --git a/scripts/provision.sh b/scripts/provision.sh index 02fa99a6..174a0be0 100755 --- a/scripts/provision.sh +++ b/scripts/provision.sh @@ -10,12 +10,3 @@ sudo apt-get update -y sudo apt-get install -y docker-ce docker run hello-world - -# curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/${K8S_VERSION}/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/ -# mkdir -p ${HOME}/.kube -# touch ${HOME}/.kube/confi -# - -## start fx proxy agent -docker run -d --name=fx-agent --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:8866:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock - diff --git a/scripts/test_cli.sh b/scripts/test_cli.sh index bb9ed0e3..20e231be 100755 --- a/scripts/test_cli.sh +++ b/scripts/test_cli.sh @@ -3,14 +3,15 @@ set -e fx="./build/fx" -service='fx-service-abc' +service='fx-service' run() { local lang=$1 local port=$2 + # localhost $fx up --name ${service}_${lang} --port ${port} --healthcheck test/functions/func.${lang} $fx list # | jq '' - $fx down ${service}_${lang} # | grep "Down Service ${service}" + $fx down ${service}_${lang} || true } build_image() { @@ -27,9 +28,8 @@ export_image() { # main # clean up -docker stop fx-agent || true && docker rm fx-agent || true +# docker stop fx-agent || true && docker rm fx-agent || true -$fx infra activate localhost port=20000 for lang in 'js' 'rb' 'py' 'go' 'php' 'java' 'd'; do run $lang $port diff --git a/utils/docker_test.go b/utils/docker_test.go deleted file mode 100644 index 28146be8..00000000 --- a/utils/docker_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package utils - -import "testing" - -func TestDockerVersion(t *testing.T) { - host := "localhost" - port := "8866" - version, err := DockerVersion(host, port) - if err != nil { - t.Fatal(err) - } - if version == "" { - t.Fatal("should version empty") - } -}