From b6c4de1832f1f6b4c42338b119dc2470383177f5 Mon Sep 17 00:00:00 2001 From: Sreekanth Date: Wed, 8 Jan 2025 18:38:34 +0530 Subject: [PATCH 1/9] fix: Initialize rustls's CryptoProvider early in the code (#2312) Signed-off-by: Sreekanth --- rust/Cargo.lock | 1 + rust/numaflow-core/Cargo.toml | 3 ++- rust/numaflow-core/src/source/serving.rs | 5 +++++ rust/numaflow/Cargo.toml | 1 + rust/numaflow/src/main.rs | 6 ++++++ rust/serving/src/app/jetstream_proxy.rs | 6 ------ rust/serving/src/lib.rs | 3 --- rust/serving/src/pipeline.rs | 2 +- 8 files changed, 16 insertions(+), 11 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index e3d90e2f0..3eddaaa6c 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1708,6 +1708,7 @@ dependencies = [ "backoff", "numaflow-core", "numaflow-models", + "rustls 0.23.19", "servesink", "serving", "tokio", diff --git a/rust/numaflow-core/Cargo.toml b/rust/numaflow-core/Cargo.toml index 4a98303a1..33c7e1cbe 100644 --- a/rust/numaflow-core/Cargo.toml +++ b/rust/numaflow-core/Cargo.toml @@ -6,7 +6,8 @@ edition = "2021" [features] nats-tests = [] pulsar-tests = [] -all-tests = ["nats-tests", "pulsar-tests"] +redis-tests = [] +all-tests = ["nats-tests", "pulsar-tests", "redis-tests"] [lints] workspace = true diff --git a/rust/numaflow-core/src/source/serving.rs b/rust/numaflow-core/src/source/serving.rs index b9fb6c72e..431cfbba3 100644 --- a/rust/numaflow-core/src/source/serving.rs +++ b/rust/numaflow-core/src/source/serving.rs @@ -139,6 +139,7 @@ mod tests { } } + #[cfg(feature = "redis-tests")] #[tokio::test] async fn test_serving_source_reader_acker() -> Result<()> { let settings = Settings { @@ -146,6 +147,10 @@ mod tests { ..Default::default() }; let settings = Arc::new(settings); + // Setup the CryptoProvider (controls core cryptography used by rustls) for the process + // ServingSource starts an Axum HTTPS server in the background. Rustls is used to generate + // self-signed certs when starting the server. + let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); let mut serving_source = ServingSource::new( Arc::clone(&settings), 10, diff --git a/rust/numaflow/Cargo.toml b/rust/numaflow/Cargo.toml index 6d5fc0dd6..58de96a9c 100644 --- a/rust/numaflow/Cargo.toml +++ b/rust/numaflow/Cargo.toml @@ -14,4 +14,5 @@ numaflow-models.workspace = true backoff.workspace = true tokio.workspace = true tracing.workspace = true +rustls.workspace = true tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } \ No newline at end of file diff --git a/rust/numaflow/src/main.rs b/rust/numaflow/src/main.rs index 9a5ab6fe8..e0836ce21 100644 --- a/rust/numaflow/src/main.rs +++ b/rust/numaflow/src/main.rs @@ -19,6 +19,12 @@ async fn main() -> Result<(), Box> { ) .with(tracing_subscriber::fmt::layer().with_ansi(false)) .init(); + + // Setup the CryptoProvider (controls core cryptography used by rustls) for the process + rustls::crypto::aws_lc_rs::default_provider() + .install_default() + .expect("Installing default CryptoProvider"); + if let Err(e) = run().await { error!("{e:?}"); return Err(e); diff --git a/rust/serving/src/app/jetstream_proxy.rs b/rust/serving/src/app/jetstream_proxy.rs index 6f61a0530..eb083d57e 100644 --- a/rust/serving/src/app/jetstream_proxy.rs +++ b/rust/serving/src/app/jetstream_proxy.rs @@ -32,7 +32,6 @@ use crate::{app::callback::state, Message, MessageWrapper}; // "from_vertex": "a" // } -const CALLBACK_URL_KEY: &str = "X-Numaflow-Callback-Url"; const NUMAFLOW_RESP_ARRAY_LEN: &str = "Numaflow-Array-Len"; const NUMAFLOW_RESP_ARRAY_IDX_LEN: &str = "Numaflow-Array-Index-Len"; @@ -40,7 +39,6 @@ struct ProxyState { message: mpsc::Sender, tid_header: String, callback: state::State, - callback_url: String, } pub(crate) async fn jetstream_proxy( @@ -50,10 +48,6 @@ pub(crate) async fn jetstream_proxy( message: state.message.clone(), tid_header: state.settings.tid_header.clone(), callback: state.callback_state.clone(), - callback_url: format!( - "https://{}:{}/v1/process/callback", - state.settings.host_ip, state.settings.app_listen_port - ), }); let router = Router::new() diff --git a/rust/serving/src/lib.rs b/rust/serving/src/lib.rs index 001065ddf..bdc3aeab9 100644 --- a/rust/serving/src/lib.rs +++ b/rust/serving/src/lib.rs @@ -39,9 +39,6 @@ pub(crate) async fn serve( where T: Clone + Send + Sync + Store + 'static, { - // Setup the CryptoProvider (controls core cryptography used by rustls) for the process - let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); - let (cert, key) = generate_certs()?; let tls_config = RustlsConfig::from_pem(cert.pem().into(), key.serialize_pem().into()) diff --git a/rust/serving/src/pipeline.rs b/rust/serving/src/pipeline.rs index cb491d7d8..cc0c2298b 100644 --- a/rust/serving/src/pipeline.rs +++ b/rust/serving/src/pipeline.rs @@ -65,7 +65,7 @@ pub(crate) struct Edge { /// DCG (directed compute graph) of the pipeline with minimal information build using vertices and edges /// from the pipeline spec #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)] -pub(crate) struct PipelineDCG { +pub struct PipelineDCG { pub(crate) vertices: Vec, pub(crate) edges: Vec, } From 97b84cffa30a3258084a11fa0ae32dac4d283de8 Mon Sep 17 00:00:00 2001 From: Sreekanth Date: Wed, 8 Jan 2025 18:57:43 +0530 Subject: [PATCH 2/9] chore: Migrate from upload/download artifacts action v3 to v4 (#2314) --- .github/workflows/nightly-build.yml | 12 ++++++------ .github/workflows/release.yml | 18 +++++++++--------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 769d7641a..072241487 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -35,7 +35,7 @@ jobs: - name: Make checksums run: make checksums - name: store artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: binaries path: dist @@ -65,7 +65,7 @@ jobs: - name: Rename binary run: cp -pv target/x86_64-unknown-linux-gnu/release/numaflow numaflow-rs-linux-amd64 - name: Upload numaflow binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: numaflow-rs-linux-amd64 path: rust/numaflow-rs-linux-amd64 @@ -98,7 +98,7 @@ jobs: - name: Rename binary run: cp -pv target/aarch64-unknown-linux-gnu/release/numaflow numaflow-rs-linux-arm64 - name: Upload numaflow binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: numaflow-rs-linux-arm64 path: rust/numaflow-rs-linux-arm64 @@ -127,19 +127,19 @@ jobs: uses: docker/setup-buildx-action@v2 - name: Download Go binaries - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: binaries path: dist/ - name: Download Rust amd64 binaries - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: numaflow-rs-linux-amd64 path: dist/ - name: Download Rust arm64 binaries - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: numaflow-rs-linux-arm64 path: dist/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index af459314e..b30fc9ca5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: - name: Make checksums run: make checksums - name: store artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: binaries path: dist @@ -56,7 +56,7 @@ jobs: - name: Rename binary run: cp -pv target/x86_64-unknown-linux-gnu/release/numaflow numaflow-rs-linux-amd64 - name: Upload numaflow binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: numaflow-rs-linux-amd64 path: rust/numaflow-rs-linux-amd64 @@ -82,7 +82,7 @@ jobs: - name: Rename binary run: cp -pv target/aarch64-unknown-linux-gnu/release/numaflow numaflow-rs-linux-arm64 - name: Upload numaflow binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: numaflow-rs-linux-arm64 path: rust/numaflow-rs-linux-arm64 @@ -111,19 +111,19 @@ jobs: uses: docker/setup-buildx-action@v2 - name: Download Go binaries - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: binaries path: dist/ - name: Download Rust amd64 binaries - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: numaflow-rs-linux-amd64 path: dist/ - name: Download Rust arm64 binaries - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: numaflow-rs-linux-arm64 path: dist/ @@ -171,7 +171,7 @@ jobs: - run: bom generate --image quay.io/numaproj/numaflow:$VERSION -o /tmp/numaflow.spdx # pack the boms into one file to make it easy to download - run: cd /tmp && tar -zcf sbom.tar.gz *.spdx - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: sbom.tar.gz path: /tmp/sbom.tar.gz @@ -191,11 +191,11 @@ jobs: echo "VERSION=${GITHUB_REF##*/}" >> $GITHUB_ENV fi - name: Download binaries - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: binaries path: dist/ - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: sbom.tar.gz path: /tmp From cd1fcb4110d849c20c23bc0b15be1e01bd984b1d Mon Sep 17 00:00:00 2001 From: Derek Wang Date: Wed, 8 Jan 2025 10:42:20 -0800 Subject: [PATCH 3/9] test: more e2e test for monovertex (#2313) Signed-off-by: Derek Wang --- test/fixtures/expect.go | 9 ++++++ test/fixtures/util.go | 22 ++++++++++++- test/fixtures/when.go | 44 ++++++++++++++++++++++++++ test/monovertex-e2e/monovertex_test.go | 16 +++++++++- 4 files changed, 89 insertions(+), 2 deletions(-) diff --git a/test/fixtures/expect.go b/test/fixtures/expect.go index afa30447d..996d762bd 100644 --- a/test/fixtures/expect.go +++ b/test/fixtures/expect.go @@ -204,6 +204,15 @@ func (t *Expect) DaemonPodLogContains(pipelineName, regex string, opts ...PodLog return t } +func (t *Expect) MvtxDaemonPodsRunning() *Expect { + t.t.Helper() + timeout := 2 * time.Minute + if err := WaitForMvtxDaemonPodsRunning(t.kubeClient, Namespace, t.monoVertex.Name, timeout); err != nil { + t.t.Fatalf("Expected mvtx daemon pods of %q running: %v", t.monoVertex.Name, err) + } + return t +} + func (t *Expect) When() *When { return &When{ t: t.t, diff --git a/test/fixtures/util.go b/test/fixtures/util.go index 13a402638..d2f27dc5f 100644 --- a/test/fixtures/util.go +++ b/test/fixtures/util.go @@ -280,7 +280,7 @@ func WaitForMonoVertexPodRunning(kubeClient kubernetes.Interface, monoVertexClie } ok := len(podList.Items) > 0 && len(podList.Items) == monoVertex.CalculateReplicas() // pod number should equal to desired replicas for _, p := range podList.Items { - ok = ok && p.Status.Phase == corev1.PodRunning + ok = ok && isPodReady(p) } if ok { return nil @@ -378,6 +378,26 @@ func WaitForDaemonPodsRunning(kubeClient kubernetes.Interface, namespace, pipeli } } +func WaitForMvtxDaemonPodsRunning(kubeClient kubernetes.Interface, namespace, mvtx string, timeout time.Duration) error { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + labelSelector := fmt.Sprintf("%s=%s,%s=%s", dfv1.KeyMonoVertexName, mvtx, dfv1.KeyComponent, dfv1.ComponentMonoVertexDaemon) + for { + podList, err := kubeClient.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: labelSelector, FieldSelector: "status.phase=Running"}) + if err != nil { + return fmt.Errorf("error getting mvtx daemon pod name: %w", err) + } + ok := len(podList.Items) > 0 + for _, p := range podList.Items { + ok = ok && p.Status.Phase == corev1.PodRunning + } + if ok { + return nil + } + time.Sleep(2 * time.Second) + } +} + func VertexPodLogNotContains(ctx context.Context, kubeClient kubernetes.Interface, namespace, pipelineName, vertexName, regex string, opts ...PodLogCheckOption) (bool, error) { labelSelector := fmt.Sprintf("%s=%s,%s=%s", dfv1.KeyPipelineName, pipelineName, dfv1.KeyVertexName, vertexName) podList, err := kubeClient.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: labelSelector, FieldSelector: "status.phase=Running"}) diff --git a/test/fixtures/when.go b/test/fixtures/when.go index 986085bdd..154c7cfda 100644 --- a/test/fixtures/when.go +++ b/test/fixtures/when.go @@ -243,6 +243,50 @@ func (w *When) DaemonPodPortForward(pipelineName string, localPort, remotePort i return w } +func (w *When) MonoVertexPodPortForward(localPort, remotePort int) *When { + w.t.Helper() + labelSelector := fmt.Sprintf("%s=%s,%s=%s", dfv1.KeyComponent, dfv1.ComponentMonoVertex, dfv1.KeyMonoVertexName, w.monoVertex.Name) + ctx := context.Background() + podList, err := w.kubeClient.CoreV1().Pods(Namespace).List(ctx, metav1.ListOptions{LabelSelector: labelSelector, FieldSelector: "status.phase=Running"}) + if err != nil { + w.t.Fatalf("Error getting mvtx pod name: %v", err) + } + podName := podList.Items[0].GetName() + w.t.Logf("MonoVertex POD name: %s", podName) + + stopCh := make(chan struct{}, 1) + if err = PodPortForward(w.restConfig, Namespace, podName, localPort, remotePort, stopCh); err != nil { + w.t.Fatalf("Expected mvtx pod port-forward: %v", err) + } + if w.portForwarderStopChannels == nil { + w.portForwarderStopChannels = make(map[string]chan struct{}) + } + w.portForwarderStopChannels[podName] = stopCh + return w +} + +func (w *When) MvtxDaemonPodPortForward(localPort, remotePort int) *When { + w.t.Helper() + labelSelector := fmt.Sprintf("%s=%s,%s=%s", dfv1.KeyComponent, dfv1.ComponentMonoVertexDaemon, dfv1.KeyMonoVertexName, w.monoVertex.Name) + ctx := context.Background() + podList, err := w.kubeClient.CoreV1().Pods(Namespace).List(ctx, metav1.ListOptions{LabelSelector: labelSelector, FieldSelector: "status.phase=Running"}) + if err != nil { + w.t.Fatalf("Error getting mvtx daemon pod name: %v", err) + } + podName := podList.Items[0].GetName() + w.t.Logf("MonoVertex Daemon POD name: %s", podName) + + stopCh := make(chan struct{}, 1) + if err = PodPortForward(w.restConfig, Namespace, podName, localPort, remotePort, stopCh); err != nil { + w.t.Fatalf("Expected mvtx daemon pod port-forward: %v", err) + } + if w.portForwarderStopChannels == nil { + w.portForwarderStopChannels = make(map[string]chan struct{}) + } + w.portForwarderStopChannels[podName] = stopCh + return w +} + func (w *When) UXServerPodPortForward(localPort, remotePort int) *When { w.t.Helper() labelSelector := fmt.Sprintf("%s=%s", dfv1.KeyComponent, dfv1.ComponentUXServer) diff --git a/test/monovertex-e2e/monovertex_test.go b/test/monovertex-e2e/monovertex_test.go index 3fd72d655..ba0c64052 100644 --- a/test/monovertex-e2e/monovertex_test.go +++ b/test/monovertex-e2e/monovertex_test.go @@ -23,6 +23,7 @@ import ( "github.com/stretchr/testify/suite" + dfv1 "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1" . "github.com/numaproj/numaflow/test/fixtures" ) @@ -35,7 +36,20 @@ func (s *MonoVertexSuite) TestMonoVertexWithTransformer() { When().CreateMonoVertexAndWait() defer w.DeleteMonoVertexAndWait() - w.Expect().MonoVertexPodsRunning() + w.Expect().MonoVertexPodsRunning().MvtxDaemonPodsRunning() + + defer w.MonoVertexPodPortForward(8931, dfv1.MonoVertexMetricsPort). + MvtxDaemonPodPortForward(3232, dfv1.MonoVertexDaemonServicePort). + TerminateAllPodPortForwards() + + // Check metrics endpoints + HTTPExpect(s.T(), "https://localhost:8931").GET("/metrics"). + Expect(). + Status(200) + + HTTPExpect(s.T(), "https://localhost:3232").GET("/metrics"). + Expect(). + Status(200) // Expect the messages to be processed by the transformer. w.Expect().MonoVertexPodLogContains("AssignEventTime", PodLogCheckOptionWithContainer("transformer")) From ade70d8da310729129708781119a5634c65f9df0 Mon Sep 17 00:00:00 2001 From: Derek Wang Date: Sun, 12 Jan 2025 14:28:54 -0800 Subject: [PATCH 4/9] fix: lastScaledAt not updated during autoscaling down (#2323) --- pkg/reconciler/monovertex/controller.go | 4 ++-- pkg/reconciler/vertex/controller.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/reconciler/monovertex/controller.go b/pkg/reconciler/monovertex/controller.go index dbe63c6ca..e0ba7cac4 100644 --- a/pkg/reconciler/monovertex/controller.go +++ b/pkg/reconciler/monovertex/controller.go @@ -206,7 +206,8 @@ func (mr *monoVertexReconciler) orchestratePods(ctx context.Context, monoVtx *df if err := mr.cleanUpPodsFromTo(ctx, monoVtx, desiredReplicas, math.MaxInt); err != nil { return fmt.Errorf("failed to clean up mono vertex pods [%v, ∞): %w", desiredReplicas, err) } - if currentReplicas := int(monoVtx.Status.Replicas); currentReplicas > desiredReplicas { + currentReplicas := int(monoVtx.Status.Replicas) + if currentReplicas > desiredReplicas { monoVtx.Status.Replicas = uint32(desiredReplicas) } updatedReplicas := int(monoVtx.Status.UpdatedReplicas) @@ -286,7 +287,6 @@ func (mr *monoVertexReconciler) orchestratePods(ctx context.Context, monoVtx *df } } - currentReplicas := int(monoVtx.Status.Replicas) if currentReplicas != desiredReplicas { log.Infow("MonoVertex replicas changed", "currentReplicas", currentReplicas, "desiredReplicas", desiredReplicas) mr.recorder.Eventf(monoVtx, corev1.EventTypeNormal, "ReplicasScaled", "Replicas changed from %d to %d", currentReplicas, desiredReplicas) diff --git a/pkg/reconciler/vertex/controller.go b/pkg/reconciler/vertex/controller.go index c5e7fdcfe..393a57395 100644 --- a/pkg/reconciler/vertex/controller.go +++ b/pkg/reconciler/vertex/controller.go @@ -220,7 +220,8 @@ func (r *vertexReconciler) orchestratePods(ctx context.Context, vertex *dfv1.Ver if err := r.cleanUpPodsFromTo(ctx, vertex, desiredReplicas, math.MaxInt); err != nil { return fmt.Errorf("failed to clean up vertex pods [%v, ∞): %w", desiredReplicas, err) } - if currentReplicas := int(vertex.Status.Replicas); currentReplicas > desiredReplicas { + currentReplicas := int(vertex.Status.Replicas) + if currentReplicas > desiredReplicas { vertex.Status.Replicas = uint32(desiredReplicas) } updatedReplicas := int(vertex.Status.UpdatedReplicas) @@ -300,7 +301,6 @@ func (r *vertexReconciler) orchestratePods(ctx context.Context, vertex *dfv1.Ver } } - currentReplicas := int(vertex.Status.Replicas) if currentReplicas != desiredReplicas { log.Infow("Pipeline Vertex replicas changed", "currentReplicas", currentReplicas, "desiredReplicas", desiredReplicas) r.recorder.Eventf(vertex, corev1.EventTypeNormal, "ReplicasScaled", "Replicas changed from %d to %d", currentReplicas, desiredReplicas) From 292217375f62e09e8c0ec048395a6fb32b3fb69d Mon Sep 17 00:00:00 2001 From: Keran Yang Date: Mon, 13 Jan 2025 18:33:27 -0500 Subject: [PATCH 5/9] chore: skip publishing the last empty struct when generator stops (#2327) --- pkg/sources/generator/tickgen.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/sources/generator/tickgen.go b/pkg/sources/generator/tickgen.go index e2e96fec9..a922e6557 100644 --- a/pkg/sources/generator/tickgen.go +++ b/pkg/sources/generator/tickgen.go @@ -197,7 +197,11 @@ loop: // since the Read call is blocking, and runs in an infinite loop, // we implement Read With Wait semantics select { - case r := <-mg.srcChan: + case r, ok := <-mg.srcChan: + if !ok { + mg.logger.Info("All the messages have been read. returning.") + break loop + } msgs = append(msgs, mg.newReadMessage(r.key, r.data, r.offset, r.ts)) case <-timeout: break loop From 2653fb248b19cbce4becdd348580a430a8950a20 Mon Sep 17 00:00:00 2001 From: SzymonZebrowski <25105608+SzymonZebrowski@users.noreply.github.com> Date: Tue, 14 Jan 2025 19:24:14 +0100 Subject: [PATCH 6/9] feat: Added support for kafka OAuth authentication (#2257) Signed-off-by: SzymonZebrowski <25105608+SzymonZebrowski@users.noreply.github.com> --- api/json-schema/schema.json | 26 + api/openapi-spec/swagger.json | 26 + .../numaflow.numaproj.io_monovertices.yaml | 105 ++ .../full/numaflow.numaproj.io_pipelines.yaml | 105 ++ .../full/numaflow.numaproj.io_vertices.yaml | 105 ++ config/install.yaml | 315 ++++ config/namespace-install.yaml | 315 ++++ docs/APIs.md | 119 ++ docs/user-guide/sinks/kafka.md | 10 +- docs/user-guide/sources/kafka.md | 10 +- pkg/apis/numaflow/v1alpha1/generated.pb.go | 1423 ++++++++++------- pkg/apis/numaflow/v1alpha1/generated.proto | 15 + pkg/apis/numaflow/v1alpha1/sasl.go | 12 + .../v1alpha1/zz_generated.deepcopy.go | 31 + .../numaflow/v1alpha1/zz_generated.openapi.go | 44 +- pkg/shared/util/sasl_config.go | 64 + pkg/shared/util/sasl_config_test.go | 37 +- rust/numaflow-models/src/models/mod.rs | 2 + rust/numaflow-models/src/models/sasl.rs | 3 + rust/numaflow-models/src/models/saslo_auth.rs | 42 + 20 files changed, 2255 insertions(+), 554 deletions(-) create mode 100644 rust/numaflow-models/src/models/saslo_auth.rs diff --git a/api/json-schema/schema.json b/api/json-schema/schema.json index 5255c9b51..b6ba9f162 100644 --- a/api/json-schema/schema.json +++ b/api/json-schema/schema.json @@ -21790,6 +21790,10 @@ "description": "SASL mechanism to use", "type": "string" }, + "oauth": { + "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.SASLOAuth", + "description": "OAuth contains the oauth config" + }, "plain": { "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.SASLPlain", "description": "SASLPlain contains the sasl plain config" @@ -21808,6 +21812,28 @@ ], "type": "object" }, + "io.numaproj.numaflow.v1alpha1.SASLOAuth": { + "properties": { + "clientID": { + "$ref": "#/definitions/io.k8s.api.core.v1.SecretKeySelector", + "description": "ClientID refers to the secret that contains the client id" + }, + "clientSecret": { + "$ref": "#/definitions/io.k8s.api.core.v1.SecretKeySelector", + "description": "ClientSecret refers to the secret that contains the client secret" + }, + "tokenEndpoint": { + "description": "TokenEndpoint refers to the token endpoint", + "type": "string" + } + }, + "required": [ + "clientID", + "clientSecret", + "tokenEndpoint" + ], + "type": "object" + }, "io.numaproj.numaflow.v1alpha1.SASLPlain": { "properties": { "handshake": { diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index f85fb66f1..2d0b9dd22 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -21780,6 +21780,10 @@ "description": "SASL mechanism to use", "type": "string" }, + "oauth": { + "description": "OAuth contains the oauth config", + "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.SASLOAuth" + }, "plain": { "description": "SASLPlain contains the sasl plain config", "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.SASLPlain" @@ -21794,6 +21798,28 @@ } } }, + "io.numaproj.numaflow.v1alpha1.SASLOAuth": { + "type": "object", + "required": [ + "clientID", + "clientSecret", + "tokenEndpoint" + ], + "properties": { + "clientID": { + "description": "ClientID refers to the secret that contains the client id", + "$ref": "#/definitions/io.k8s.api.core.v1.SecretKeySelector" + }, + "clientSecret": { + "description": "ClientSecret refers to the secret that contains the client secret", + "$ref": "#/definitions/io.k8s.api.core.v1.SecretKeySelector" + }, + "tokenEndpoint": { + "description": "TokenEndpoint refers to the token endpoint", + "type": "string" + } + } + }, "io.numaproj.numaflow.v1alpha1.SASLPlain": { "type": "object", "required": [ diff --git a/config/base/crds/full/numaflow.numaproj.io_monovertices.yaml b/config/base/crds/full/numaflow.numaproj.io_monovertices.yaml index a9ff56ca3..092cea6ff 100644 --- a/config/base/crds/full/numaflow.numaproj.io_monovertices.yaml +++ b/config/base/crds/full/numaflow.numaproj.io_monovertices.yaml @@ -3474,6 +3474,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -4009,6 +4044,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -4723,6 +4793,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: diff --git a/config/base/crds/full/numaflow.numaproj.io_pipelines.yaml b/config/base/crds/full/numaflow.numaproj.io_pipelines.yaml index 3e06abece..0162e93ab 100644 --- a/config/base/crds/full/numaflow.numaproj.io_pipelines.yaml +++ b/config/base/crds/full/numaflow.numaproj.io_pipelines.yaml @@ -8158,6 +8158,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -8693,6 +8728,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -9407,6 +9477,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: diff --git a/config/base/crds/full/numaflow.numaproj.io_vertices.yaml b/config/base/crds/full/numaflow.numaproj.io_vertices.yaml index 5f80efaf5..4fb2d2d56 100644 --- a/config/base/crds/full/numaflow.numaproj.io_vertices.yaml +++ b/config/base/crds/full/numaflow.numaproj.io_vertices.yaml @@ -2942,6 +2942,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -3477,6 +3512,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -4191,6 +4261,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: diff --git a/config/install.yaml b/config/install.yaml index 6410435be..250d9a572 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -6661,6 +6661,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -7196,6 +7231,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -7910,6 +7980,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -17931,6 +18036,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -18466,6 +18606,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -19180,6 +19355,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -24376,6 +24586,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -24911,6 +25156,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -25625,6 +25905,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: diff --git a/config/namespace-install.yaml b/config/namespace-install.yaml index 25de004c4..215639d52 100644 --- a/config/namespace-install.yaml +++ b/config/namespace-install.yaml @@ -6661,6 +6661,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -7196,6 +7231,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -7910,6 +7980,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -17931,6 +18036,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -18466,6 +18606,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -19180,6 +19355,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -24376,6 +24586,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -24911,6 +25156,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: @@ -25625,6 +25905,41 @@ spec: type: object mechanism: type: string + oauth: + properties: + clientID: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenEndpoint: + type: string + required: + - clientID + - clientSecret + - tokenEndpoint + type: object plain: properties: handshake: diff --git a/docs/APIs.md b/docs/APIs.md index fb07d8291..a3e3ba1e1 100644 --- a/docs/APIs.md +++ b/docs/APIs.md @@ -9304,6 +9304,125 @@ SASLSCRAMSHA512 contains the sasl plain config + + + + +oauth
+ SASLOAuth + + + + +(Optional) +

+ +OAuth contains the oauth config +

+ + + + + + + + + +

+ +SASLOAuth +

+ +

+ +(Appears on: +SASL) +

+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +Field + + +Description +
+ +clientID
+ +Kubernetes core/v1.SecretKeySelector +
+ +

+ +ClientID refers to the secret that contains the client id +

+ +
+ +clientSecret
+ +Kubernetes core/v1.SecretKeySelector +
+ +

+ +ClientSecret refers to the secret that contains the client secret +

+ +
+ +tokenEndpoint
string +
+ +

+ +TokenEndpoint refers to the token endpoint +

+ +
diff --git a/docs/user-guide/sinks/kafka.md b/docs/user-guide/sinks/kafka.md index 6837f262f..6b610b828 100644 --- a/docs/user-guide/sinks/kafka.md +++ b/docs/user-guide/sinks/kafka.md @@ -34,7 +34,7 @@ spec: name: my-pk key: my-pk-key sasl: # Optional - mechanism: GSSAPI # PLAIN, GSSAPI, SCRAM-SHA-256 or SCRAM-SHA-512, other mechanisms not supported + mechanism: GSSAPI # PLAIN, GSSAPI, OAUTHBEARER, SCRAM-SHA-256 or SCRAM-SHA-512 other mechanisms not supported gssapi: # Optional, for GSSAPI mechanism serviceName: my-service realm: my-realm @@ -85,6 +85,14 @@ spec: # Send the Kafka SASL handshake first if enabled (defaults to true) # Set this to false if using a non-Kafka SASL proxy handshake: true + oauth: #Optional, for OAUTHBEARER mechanism + clientID: # Pointing to a secret reference which contains the client id + name: kafka-oauth-client + key: clientid + clientSecret: # Pointing to a secret reference which contains the client secret + name: kafka-oauth-client + key: clientsecret + tokenEndpoint: https://oauth-token.com/v1/token # Optional, a yaml format string which could apply more configuration for the sink. # The configuration hierarchy follows the Struct of sarama.Config at https://github.com/IBM/sarama/blob/main/config.go. config: | diff --git a/docs/user-guide/sources/kafka.md b/docs/user-guide/sources/kafka.md index f0e9f61c3..314d72fde 100644 --- a/docs/user-guide/sources/kafka.md +++ b/docs/user-guide/sources/kafka.md @@ -79,7 +79,15 @@ spec: key: scram-sha-512-password-key # Send the Kafka SASL handshake first if enabled (defaults to true) # Set this to false if using a non-Kafka SASL proxy - handshake: true + handshake: true + oauth: #Optional, for OAUTHBEARER mechanism + clientID: # Pointing to a secret reference which contains the client id + name: kafka-oauth-client + key: clientid + clientSecret: # Pointing to a secret reference which contains the client secret + name: kafka-oauth-client + key: clientsecret + tokenEndpoint: https://oauth-token.com/v1/token ``` ## FAQ diff --git a/pkg/apis/numaflow/v1alpha1/generated.pb.go b/pkg/apis/numaflow/v1alpha1/generated.pb.go index 0a90cd05d..a03d2ec9d 100644 --- a/pkg/apis/numaflow/v1alpha1/generated.pb.go +++ b/pkg/apis/numaflow/v1alpha1/generated.pb.go @@ -1982,10 +1982,38 @@ func (m *SASL) XXX_DiscardUnknown() { var xxx_messageInfo_SASL proto.InternalMessageInfo +func (m *SASLOAuth) Reset() { *m = SASLOAuth{} } +func (*SASLOAuth) ProtoMessage() {} +func (*SASLOAuth) Descriptor() ([]byte, []int) { + return fileDescriptor_9d0d1b17d3865563, []int{69} +} +func (m *SASLOAuth) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *SASLOAuth) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *SASLOAuth) XXX_Merge(src proto.Message) { + xxx_messageInfo_SASLOAuth.Merge(m, src) +} +func (m *SASLOAuth) XXX_Size() int { + return m.Size() +} +func (m *SASLOAuth) XXX_DiscardUnknown() { + xxx_messageInfo_SASLOAuth.DiscardUnknown(m) +} + +var xxx_messageInfo_SASLOAuth proto.InternalMessageInfo + func (m *SASLPlain) Reset() { *m = SASLPlain{} } func (*SASLPlain) ProtoMessage() {} func (*SASLPlain) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{69} + return fileDescriptor_9d0d1b17d3865563, []int{70} } func (m *SASLPlain) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2013,7 +2041,7 @@ var xxx_messageInfo_SASLPlain proto.InternalMessageInfo func (m *Scale) Reset() { *m = Scale{} } func (*Scale) ProtoMessage() {} func (*Scale) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{70} + return fileDescriptor_9d0d1b17d3865563, []int{71} } func (m *Scale) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2041,7 +2069,7 @@ var xxx_messageInfo_Scale proto.InternalMessageInfo func (m *ServingSource) Reset() { *m = ServingSource{} } func (*ServingSource) ProtoMessage() {} func (*ServingSource) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{71} + return fileDescriptor_9d0d1b17d3865563, []int{72} } func (m *ServingSource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2069,7 +2097,7 @@ var xxx_messageInfo_ServingSource proto.InternalMessageInfo func (m *ServingStore) Reset() { *m = ServingStore{} } func (*ServingStore) ProtoMessage() {} func (*ServingStore) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{72} + return fileDescriptor_9d0d1b17d3865563, []int{73} } func (m *ServingStore) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2097,7 +2125,7 @@ var xxx_messageInfo_ServingStore proto.InternalMessageInfo func (m *SessionWindow) Reset() { *m = SessionWindow{} } func (*SessionWindow) ProtoMessage() {} func (*SessionWindow) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{73} + return fileDescriptor_9d0d1b17d3865563, []int{74} } func (m *SessionWindow) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2125,7 +2153,7 @@ var xxx_messageInfo_SessionWindow proto.InternalMessageInfo func (m *SideInput) Reset() { *m = SideInput{} } func (*SideInput) ProtoMessage() {} func (*SideInput) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{74} + return fileDescriptor_9d0d1b17d3865563, []int{75} } func (m *SideInput) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2153,7 +2181,7 @@ var xxx_messageInfo_SideInput proto.InternalMessageInfo func (m *SideInputTrigger) Reset() { *m = SideInputTrigger{} } func (*SideInputTrigger) ProtoMessage() {} func (*SideInputTrigger) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{75} + return fileDescriptor_9d0d1b17d3865563, []int{76} } func (m *SideInputTrigger) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2181,7 +2209,7 @@ var xxx_messageInfo_SideInputTrigger proto.InternalMessageInfo func (m *SideInputsManagerTemplate) Reset() { *m = SideInputsManagerTemplate{} } func (*SideInputsManagerTemplate) ProtoMessage() {} func (*SideInputsManagerTemplate) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{76} + return fileDescriptor_9d0d1b17d3865563, []int{77} } func (m *SideInputsManagerTemplate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2209,7 +2237,7 @@ var xxx_messageInfo_SideInputsManagerTemplate proto.InternalMessageInfo func (m *Sink) Reset() { *m = Sink{} } func (*Sink) ProtoMessage() {} func (*Sink) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{77} + return fileDescriptor_9d0d1b17d3865563, []int{78} } func (m *Sink) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2237,7 +2265,7 @@ var xxx_messageInfo_Sink proto.InternalMessageInfo func (m *SlidingWindow) Reset() { *m = SlidingWindow{} } func (*SlidingWindow) ProtoMessage() {} func (*SlidingWindow) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{78} + return fileDescriptor_9d0d1b17d3865563, []int{79} } func (m *SlidingWindow) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2265,7 +2293,7 @@ var xxx_messageInfo_SlidingWindow proto.InternalMessageInfo func (m *Source) Reset() { *m = Source{} } func (*Source) ProtoMessage() {} func (*Source) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{79} + return fileDescriptor_9d0d1b17d3865563, []int{80} } func (m *Source) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2293,7 +2321,7 @@ var xxx_messageInfo_Source proto.InternalMessageInfo func (m *Status) Reset() { *m = Status{} } func (*Status) ProtoMessage() {} func (*Status) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{80} + return fileDescriptor_9d0d1b17d3865563, []int{81} } func (m *Status) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2321,7 +2349,7 @@ var xxx_messageInfo_Status proto.InternalMessageInfo func (m *TLS) Reset() { *m = TLS{} } func (*TLS) ProtoMessage() {} func (*TLS) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{81} + return fileDescriptor_9d0d1b17d3865563, []int{82} } func (m *TLS) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2349,7 +2377,7 @@ var xxx_messageInfo_TLS proto.InternalMessageInfo func (m *TagConditions) Reset() { *m = TagConditions{} } func (*TagConditions) ProtoMessage() {} func (*TagConditions) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{82} + return fileDescriptor_9d0d1b17d3865563, []int{83} } func (m *TagConditions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2377,7 +2405,7 @@ var xxx_messageInfo_TagConditions proto.InternalMessageInfo func (m *Templates) Reset() { *m = Templates{} } func (*Templates) ProtoMessage() {} func (*Templates) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{83} + return fileDescriptor_9d0d1b17d3865563, []int{84} } func (m *Templates) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2405,7 +2433,7 @@ var xxx_messageInfo_Templates proto.InternalMessageInfo func (m *Transformer) Reset() { *m = Transformer{} } func (*Transformer) ProtoMessage() {} func (*Transformer) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{84} + return fileDescriptor_9d0d1b17d3865563, []int{85} } func (m *Transformer) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2433,7 +2461,7 @@ var xxx_messageInfo_Transformer proto.InternalMessageInfo func (m *UDF) Reset() { *m = UDF{} } func (*UDF) ProtoMessage() {} func (*UDF) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{85} + return fileDescriptor_9d0d1b17d3865563, []int{86} } func (m *UDF) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2461,7 +2489,7 @@ var xxx_messageInfo_UDF proto.InternalMessageInfo func (m *UDSink) Reset() { *m = UDSink{} } func (*UDSink) ProtoMessage() {} func (*UDSink) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{86} + return fileDescriptor_9d0d1b17d3865563, []int{87} } func (m *UDSink) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2489,7 +2517,7 @@ var xxx_messageInfo_UDSink proto.InternalMessageInfo func (m *UDSource) Reset() { *m = UDSource{} } func (*UDSource) ProtoMessage() {} func (*UDSource) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{87} + return fileDescriptor_9d0d1b17d3865563, []int{88} } func (m *UDSource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2517,7 +2545,7 @@ var xxx_messageInfo_UDSource proto.InternalMessageInfo func (m *UDTransformer) Reset() { *m = UDTransformer{} } func (*UDTransformer) ProtoMessage() {} func (*UDTransformer) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{88} + return fileDescriptor_9d0d1b17d3865563, []int{89} } func (m *UDTransformer) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2545,7 +2573,7 @@ var xxx_messageInfo_UDTransformer proto.InternalMessageInfo func (m *UpdateStrategy) Reset() { *m = UpdateStrategy{} } func (*UpdateStrategy) ProtoMessage() {} func (*UpdateStrategy) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{89} + return fileDescriptor_9d0d1b17d3865563, []int{90} } func (m *UpdateStrategy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2573,7 +2601,7 @@ var xxx_messageInfo_UpdateStrategy proto.InternalMessageInfo func (m *Vertex) Reset() { *m = Vertex{} } func (*Vertex) ProtoMessage() {} func (*Vertex) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{90} + return fileDescriptor_9d0d1b17d3865563, []int{91} } func (m *Vertex) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2601,7 +2629,7 @@ var xxx_messageInfo_Vertex proto.InternalMessageInfo func (m *VertexInstance) Reset() { *m = VertexInstance{} } func (*VertexInstance) ProtoMessage() {} func (*VertexInstance) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{91} + return fileDescriptor_9d0d1b17d3865563, []int{92} } func (m *VertexInstance) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2629,7 +2657,7 @@ var xxx_messageInfo_VertexInstance proto.InternalMessageInfo func (m *VertexLimits) Reset() { *m = VertexLimits{} } func (*VertexLimits) ProtoMessage() {} func (*VertexLimits) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{92} + return fileDescriptor_9d0d1b17d3865563, []int{93} } func (m *VertexLimits) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2657,7 +2685,7 @@ var xxx_messageInfo_VertexLimits proto.InternalMessageInfo func (m *VertexList) Reset() { *m = VertexList{} } func (*VertexList) ProtoMessage() {} func (*VertexList) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{93} + return fileDescriptor_9d0d1b17d3865563, []int{94} } func (m *VertexList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2685,7 +2713,7 @@ var xxx_messageInfo_VertexList proto.InternalMessageInfo func (m *VertexSpec) Reset() { *m = VertexSpec{} } func (*VertexSpec) ProtoMessage() {} func (*VertexSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{94} + return fileDescriptor_9d0d1b17d3865563, []int{95} } func (m *VertexSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2713,7 +2741,7 @@ var xxx_messageInfo_VertexSpec proto.InternalMessageInfo func (m *VertexStatus) Reset() { *m = VertexStatus{} } func (*VertexStatus) ProtoMessage() {} func (*VertexStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{95} + return fileDescriptor_9d0d1b17d3865563, []int{96} } func (m *VertexStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2741,7 +2769,7 @@ var xxx_messageInfo_VertexStatus proto.InternalMessageInfo func (m *VertexTemplate) Reset() { *m = VertexTemplate{} } func (*VertexTemplate) ProtoMessage() {} func (*VertexTemplate) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{96} + return fileDescriptor_9d0d1b17d3865563, []int{97} } func (m *VertexTemplate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2769,7 +2797,7 @@ var xxx_messageInfo_VertexTemplate proto.InternalMessageInfo func (m *Watermark) Reset() { *m = Watermark{} } func (*Watermark) ProtoMessage() {} func (*Watermark) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{97} + return fileDescriptor_9d0d1b17d3865563, []int{98} } func (m *Watermark) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2797,7 +2825,7 @@ var xxx_messageInfo_Watermark proto.InternalMessageInfo func (m *Window) Reset() { *m = Window{} } func (*Window) ProtoMessage() {} func (*Window) Descriptor() ([]byte, []int) { - return fileDescriptor_9d0d1b17d3865563, []int{98} + return fileDescriptor_9d0d1b17d3865563, []int{99} } func (m *Window) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2900,6 +2928,7 @@ func init() { proto.RegisterType((*RetryStrategy)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.RetryStrategy") proto.RegisterType((*RollingUpdateStrategy)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.RollingUpdateStrategy") proto.RegisterType((*SASL)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.SASL") + proto.RegisterType((*SASLOAuth)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.SASLOAuth") proto.RegisterType((*SASLPlain)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.SASLPlain") proto.RegisterType((*Scale)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.Scale") proto.RegisterType((*ServingSource)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.ServingSource") @@ -2938,526 +2967,530 @@ func init() { } var fileDescriptor_9d0d1b17d3865563 = []byte{ - // 8292 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x6f, 0x6c, 0x24, 0x47, - 0x76, 0x9f, 0xe6, 0xff, 0xcc, 0x1b, 0x92, 0x4b, 0xd5, 0xae, 0x56, 0x5c, 0xde, 0x6a, 0x67, 0xaf, - 0xcf, 0xa7, 0x5b, 0xc7, 0x67, 0x32, 0xa2, 0x4f, 0x3a, 0x9d, 0xed, 0x3b, 0x89, 0x43, 0x2e, 0x77, - 0xa9, 0x25, 0x77, 0x79, 0x6f, 0xc8, 0x95, 0xce, 0x8a, 0x4f, 0x69, 0x76, 0x17, 0x87, 0x2d, 0xf6, - 0x74, 0xcf, 0x75, 0xf7, 0x70, 0x97, 0x72, 0x8c, 0xb3, 0xef, 0x12, 0xe8, 0x82, 0x24, 0x48, 0xe0, - 0x4f, 0x06, 0x02, 0x27, 0x48, 0x10, 0xc0, 0x1f, 0x0c, 0xe7, 0x83, 0x91, 0xcb, 0x87, 0x00, 0xf9, - 0xe3, 0x20, 0x48, 0x2e, 0xff, 0x0f, 0x41, 0x80, 0x28, 0x1f, 0x42, 0xe4, 0x18, 0x04, 0x41, 0x02, - 0x24, 0x70, 0x62, 0x24, 0x76, 0x16, 0x41, 0x1c, 0xd4, 0xbf, 0xfe, 0x37, 0x3d, 0xbb, 0xe4, 0xf4, - 0x70, 0xb5, 0x72, 0xf4, 0x6d, 0xa6, 0xde, 0xab, 0xdf, 0xab, 0xae, 0xae, 0xae, 0x7a, 0xf5, 0xde, - 0xab, 0x57, 0x70, 0xab, 0x6b, 0x05, 0xfb, 0x83, 0xdd, 0x05, 0xc3, 0xed, 0x2d, 0x3a, 0x83, 0x9e, - 0xde, 0xf7, 0xdc, 0xf7, 0xf9, 0x8f, 0x3d, 0xdb, 0x7d, 0xb0, 0xd8, 0x3f, 0xe8, 0x2e, 0xea, 0x7d, - 0xcb, 0x8f, 0x4a, 0x0e, 0x5f, 0xd1, 0xed, 0xfe, 0xbe, 0xfe, 0xca, 0x62, 0x97, 0x3a, 0xd4, 0xd3, - 0x03, 0x6a, 0x2e, 0xf4, 0x3d, 0x37, 0x70, 0xc9, 0x97, 0x23, 0xa0, 0x05, 0x05, 0xb4, 0xa0, 0xaa, - 0x2d, 0xf4, 0x0f, 0xba, 0x0b, 0x0c, 0x28, 0x2a, 0x51, 0x40, 0xf3, 0x3f, 0x19, 0x6b, 0x41, 0xd7, - 0xed, 0xba, 0x8b, 0x1c, 0x6f, 0x77, 0xb0, 0xc7, 0xff, 0xf1, 0x3f, 0xfc, 0x97, 0x90, 0x33, 0xaf, - 0x1d, 0xbc, 0xee, 0x2f, 0x58, 0x2e, 0x6b, 0xd6, 0xa2, 0xe1, 0x7a, 0x74, 0xf1, 0x70, 0xa8, 0x2d, - 0xf3, 0x5f, 0x8a, 0x78, 0x7a, 0xba, 0xb1, 0x6f, 0x39, 0xd4, 0x3b, 0x52, 0xcf, 0xb2, 0xe8, 0x51, - 0xdf, 0x1d, 0x78, 0x06, 0x3d, 0x53, 0x2d, 0x7f, 0xb1, 0x47, 0x03, 0x3d, 0x4b, 0xd6, 0xe2, 0xa8, - 0x5a, 0xde, 0xc0, 0x09, 0xac, 0xde, 0xb0, 0x98, 0xd7, 0x9e, 0x54, 0xc1, 0x37, 0xf6, 0x69, 0x4f, - 0x1f, 0xaa, 0xf7, 0x53, 0xa3, 0xea, 0x0d, 0x02, 0xcb, 0x5e, 0xb4, 0x9c, 0xc0, 0x0f, 0xbc, 0x74, - 0x25, 0xed, 0xb7, 0x01, 0x2e, 0x2e, 0xef, 0xfa, 0x81, 0xa7, 0x1b, 0xc1, 0x96, 0x6b, 0x6e, 0xd3, - 0x5e, 0xdf, 0xd6, 0x03, 0x4a, 0x0e, 0xa0, 0xce, 0x1e, 0xc8, 0xd4, 0x03, 0x7d, 0xae, 0x70, 0xbd, - 0x70, 0xa3, 0xb9, 0xb4, 0xbc, 0x30, 0xe6, 0x0b, 0x5c, 0xd8, 0x94, 0x40, 0xed, 0xa9, 0x93, 0xe3, - 0x56, 0x5d, 0xfd, 0xc3, 0x50, 0x00, 0xf9, 0xd5, 0x02, 0x4c, 0x39, 0xae, 0x49, 0x3b, 0xd4, 0xa6, - 0x46, 0xe0, 0x7a, 0x73, 0xc5, 0xeb, 0xa5, 0x1b, 0xcd, 0xa5, 0x6f, 0x8e, 0x2d, 0x31, 0xe3, 0x89, - 0x16, 0xee, 0xc6, 0x04, 0xdc, 0x74, 0x02, 0xef, 0xa8, 0x7d, 0xe9, 0x07, 0xc7, 0xad, 0xe7, 0x4e, - 0x8e, 0x5b, 0x53, 0x71, 0x12, 0x26, 0x5a, 0x42, 0x76, 0xa0, 0x19, 0xb8, 0x36, 0xeb, 0x32, 0xcb, - 0x75, 0xfc, 0xb9, 0x12, 0x6f, 0xd8, 0xb5, 0x05, 0xd1, 0xd5, 0x4c, 0xfc, 0x02, 0x1b, 0x63, 0x0b, - 0x87, 0xaf, 0x2c, 0x6c, 0x87, 0x6c, 0xed, 0x8b, 0x12, 0xb8, 0x19, 0x95, 0xf9, 0x18, 0xc7, 0x21, - 0x14, 0x2e, 0xf8, 0xd4, 0x18, 0x78, 0x56, 0x70, 0xb4, 0xe2, 0x3a, 0x01, 0x7d, 0x18, 0xcc, 0x95, - 0x79, 0x2f, 0xbf, 0x9c, 0x05, 0xbd, 0xe5, 0x9a, 0x9d, 0x24, 0x77, 0xfb, 0xe2, 0xc9, 0x71, 0xeb, - 0x42, 0xaa, 0x10, 0xd3, 0x98, 0xc4, 0x81, 0x59, 0xab, 0xa7, 0x77, 0xe9, 0xd6, 0xc0, 0xb6, 0x3b, - 0xd4, 0xf0, 0x68, 0xe0, 0xcf, 0x55, 0xf8, 0x23, 0xdc, 0xc8, 0x92, 0xb3, 0xe1, 0x1a, 0xba, 0x7d, - 0x6f, 0xf7, 0x7d, 0x6a, 0x04, 0x48, 0xf7, 0xa8, 0x47, 0x1d, 0x83, 0xb6, 0xe7, 0xe4, 0xc3, 0xcc, - 0xae, 0xa7, 0x90, 0x70, 0x08, 0x9b, 0xdc, 0x82, 0xe7, 0xfb, 0x9e, 0xe5, 0xf2, 0x26, 0xd8, 0xba, - 0xef, 0xdf, 0xd5, 0x7b, 0x74, 0xae, 0x7a, 0xbd, 0x70, 0xa3, 0xd1, 0xbe, 0x22, 0x61, 0x9e, 0xdf, - 0x4a, 0x33, 0xe0, 0x70, 0x1d, 0x72, 0x03, 0xea, 0xaa, 0x70, 0xae, 0x76, 0xbd, 0x70, 0xa3, 0x22, - 0xc6, 0x8e, 0xaa, 0x8b, 0x21, 0x95, 0xac, 0x41, 0x5d, 0xdf, 0xdb, 0xb3, 0x1c, 0xc6, 0x59, 0xe7, - 0x5d, 0x78, 0x35, 0xeb, 0xd1, 0x96, 0x25, 0x8f, 0xc0, 0x51, 0xff, 0x30, 0xac, 0x4b, 0xde, 0x02, - 0xe2, 0x53, 0xef, 0xd0, 0x32, 0xe8, 0xb2, 0x61, 0xb8, 0x03, 0x27, 0xe0, 0x6d, 0x6f, 0xf0, 0xb6, - 0xcf, 0xcb, 0xb6, 0x93, 0xce, 0x10, 0x07, 0x66, 0xd4, 0x22, 0x6f, 0xc2, 0xac, 0xfc, 0x56, 0xa3, - 0x5e, 0x00, 0x8e, 0x74, 0x89, 0x75, 0x24, 0xa6, 0x68, 0x38, 0xc4, 0x4d, 0x4c, 0xb8, 0xaa, 0x0f, - 0x02, 0xb7, 0xc7, 0x20, 0x93, 0x42, 0xb7, 0xdd, 0x03, 0xea, 0xcc, 0x35, 0xaf, 0x17, 0x6e, 0xd4, - 0xdb, 0xd7, 0x4f, 0x8e, 0x5b, 0x57, 0x97, 0x1f, 0xc3, 0x87, 0x8f, 0x45, 0x21, 0xf7, 0xa0, 0x61, - 0x3a, 0xfe, 0x96, 0x6b, 0x5b, 0xc6, 0xd1, 0xdc, 0x14, 0x6f, 0xe0, 0x2b, 0xf2, 0x51, 0x1b, 0xab, - 0x77, 0x3b, 0x82, 0xf0, 0xe8, 0xb8, 0x75, 0x75, 0x78, 0x4a, 0x5d, 0x08, 0xe9, 0x18, 0x61, 0x90, - 0x4d, 0x0e, 0xb8, 0xe2, 0x3a, 0x7b, 0x56, 0x77, 0x6e, 0x9a, 0xbf, 0x8d, 0xeb, 0x23, 0x06, 0xf4, - 0xea, 0xdd, 0x8e, 0xe0, 0x6b, 0x4f, 0x4b, 0x71, 0xe2, 0x2f, 0x46, 0x08, 0xc4, 0x84, 0x19, 0x35, - 0x19, 0xaf, 0xd8, 0xba, 0xd5, 0xf3, 0xe7, 0x66, 0xf8, 0xe0, 0xfd, 0xb1, 0x11, 0x98, 0x18, 0x67, - 0x6e, 0x5f, 0x96, 0x8f, 0x32, 0x93, 0x28, 0xf6, 0x31, 0x85, 0x39, 0xff, 0x06, 0x3c, 0x3f, 0x34, - 0x37, 0x90, 0x59, 0x28, 0x1d, 0xd0, 0x23, 0x3e, 0xf5, 0x35, 0x90, 0xfd, 0x24, 0x97, 0xa0, 0x72, - 0xa8, 0xdb, 0x03, 0x3a, 0x57, 0xe4, 0x65, 0xe2, 0xcf, 0x4f, 0x17, 0x5f, 0x2f, 0x68, 0x7f, 0xb5, - 0x04, 0x53, 0x6a, 0xc6, 0xe9, 0x58, 0xce, 0x01, 0x79, 0x1b, 0x4a, 0xb6, 0xdb, 0x95, 0xf3, 0xe6, - 0xcf, 0x8e, 0x3d, 0x8b, 0x6d, 0xb8, 0xdd, 0x76, 0xed, 0xe4, 0xb8, 0x55, 0xda, 0x70, 0xbb, 0xc8, - 0x10, 0x89, 0x01, 0x95, 0x03, 0x7d, 0xef, 0x40, 0xe7, 0x6d, 0x68, 0x2e, 0xb5, 0xc7, 0x86, 0xbe, - 0xc3, 0x50, 0x58, 0x5b, 0xdb, 0x8d, 0x93, 0xe3, 0x56, 0x85, 0xff, 0x45, 0x81, 0x4d, 0x5c, 0x68, - 0xec, 0xda, 0xba, 0x71, 0xb0, 0xef, 0xda, 0x74, 0xae, 0x94, 0x53, 0x50, 0x5b, 0x21, 0x89, 0xd7, - 0x1c, 0xfe, 0xc5, 0x48, 0x06, 0x31, 0xa0, 0x3a, 0x30, 0x7d, 0xcb, 0x39, 0x90, 0x73, 0xe0, 0x1b, - 0x63, 0x4b, 0xdb, 0x59, 0xe5, 0xcf, 0x04, 0x27, 0xc7, 0xad, 0xaa, 0xf8, 0x8d, 0x12, 0x5a, 0xfb, - 0xfd, 0x29, 0x98, 0x51, 0x2f, 0xe9, 0x3e, 0xf5, 0x02, 0xfa, 0x90, 0x5c, 0x87, 0xb2, 0xc3, 0x3e, - 0x4d, 0xfe, 0x92, 0xdb, 0x53, 0x72, 0xb8, 0x94, 0xf9, 0x27, 0xc9, 0x29, 0xac, 0x65, 0x62, 0xa8, - 0xc8, 0x0e, 0x1f, 0xbf, 0x65, 0x1d, 0x0e, 0x23, 0x5a, 0x26, 0x7e, 0xa3, 0x84, 0x26, 0xef, 0x42, - 0x99, 0x3f, 0xbc, 0xe8, 0xea, 0xaf, 0x8e, 0x2f, 0x82, 0x3d, 0x7a, 0x9d, 0x3d, 0x01, 0x7f, 0x70, - 0x0e, 0xca, 0x86, 0xe2, 0xc0, 0xdc, 0x93, 0x1d, 0xfb, 0xb3, 0x39, 0x3a, 0x76, 0x4d, 0x0c, 0xc5, - 0x9d, 0xd5, 0x35, 0x64, 0x88, 0xe4, 0xcf, 0x17, 0xe0, 0x79, 0xc3, 0x75, 0x02, 0x9d, 0xe9, 0x19, - 0x6a, 0x91, 0x9d, 0xab, 0x70, 0x39, 0x6f, 0x8d, 0x2d, 0x67, 0x25, 0x8d, 0xd8, 0x7e, 0x81, 0xad, - 0x19, 0x43, 0xc5, 0x38, 0x2c, 0x9b, 0xfc, 0xc5, 0x02, 0xbc, 0xc0, 0xe6, 0xf2, 0x21, 0x66, 0xbe, - 0x02, 0x4d, 0xb6, 0x55, 0x57, 0x4e, 0x8e, 0x5b, 0x2f, 0xac, 0x67, 0x09, 0xc3, 0xec, 0x36, 0xb0, - 0xd6, 0x5d, 0xd4, 0x87, 0xd5, 0x12, 0xbe, 0xba, 0x35, 0x97, 0x36, 0x26, 0xa9, 0xea, 0xb4, 0x3f, - 0x23, 0x87, 0x72, 0x96, 0x66, 0x87, 0x59, 0xad, 0x20, 0x37, 0xa1, 0x76, 0xe8, 0xda, 0x83, 0x1e, - 0xf5, 0xe7, 0xea, 0x7c, 0x8a, 0x9d, 0xcf, 0x9a, 0x62, 0xef, 0x73, 0x96, 0xf6, 0x05, 0x09, 0x5f, - 0x13, 0xff, 0x7d, 0x54, 0x75, 0x89, 0x05, 0x55, 0xdb, 0xea, 0x59, 0x81, 0xcf, 0x17, 0xce, 0xe6, - 0xd2, 0xcd, 0xb1, 0x1f, 0x4b, 0x7c, 0xa2, 0x1b, 0x1c, 0x4c, 0x7c, 0x35, 0xe2, 0x37, 0x4a, 0x01, - 0x6c, 0x2a, 0xf4, 0x0d, 0xdd, 0x16, 0x0b, 0x6b, 0x73, 0xe9, 0x6b, 0xe3, 0x7f, 0x36, 0x0c, 0xa5, - 0x3d, 0x2d, 0x9f, 0xa9, 0xc2, 0xff, 0xa2, 0xc0, 0x26, 0x3f, 0x0f, 0x33, 0x89, 0xb7, 0xe9, 0xcf, - 0x35, 0x79, 0xef, 0xbc, 0x94, 0xd5, 0x3b, 0x21, 0x57, 0xb4, 0xf2, 0x24, 0x46, 0x88, 0x8f, 0x29, - 0x30, 0x72, 0x07, 0xea, 0xbe, 0x65, 0x52, 0x43, 0xf7, 0xfc, 0xb9, 0xa9, 0xd3, 0x00, 0xcf, 0x4a, - 0xe0, 0x7a, 0x47, 0x56, 0xc3, 0x10, 0x80, 0x2c, 0x00, 0xf4, 0x75, 0x2f, 0xb0, 0x84, 0xa2, 0x3a, - 0xcd, 0x95, 0xa6, 0x99, 0x93, 0xe3, 0x16, 0x6c, 0x85, 0xa5, 0x18, 0xe3, 0x60, 0xfc, 0xac, 0xee, - 0xba, 0xd3, 0x1f, 0x04, 0x62, 0x61, 0x6d, 0x08, 0xfe, 0x4e, 0x58, 0x8a, 0x31, 0x0e, 0xf2, 0x9b, - 0x05, 0xf8, 0x4c, 0xf4, 0x77, 0xf8, 0x23, 0xbb, 0x30, 0xf1, 0x8f, 0xac, 0x75, 0x72, 0xdc, 0xfa, - 0x4c, 0x67, 0xb4, 0x48, 0x7c, 0x5c, 0x7b, 0xc8, 0x87, 0x05, 0x98, 0x19, 0xf4, 0x4d, 0x3d, 0xa0, - 0x9d, 0x80, 0xed, 0x78, 0xba, 0x47, 0x73, 0xb3, 0xbc, 0x89, 0xb7, 0xc6, 0x9f, 0x05, 0x13, 0x70, - 0xd1, 0x6b, 0x4e, 0x96, 0x63, 0x4a, 0xac, 0xf6, 0x36, 0x4c, 0x2f, 0x0f, 0x82, 0x7d, 0xd7, 0xb3, - 0x3e, 0xe0, 0xea, 0x3f, 0x59, 0x83, 0x4a, 0xc0, 0xd5, 0x38, 0xa1, 0x21, 0x7c, 0x3e, 0xeb, 0xa5, - 0x0b, 0x95, 0xfa, 0x0e, 0x3d, 0x52, 0x7a, 0x89, 0x58, 0xa9, 0x85, 0x5a, 0x27, 0xaa, 0x6b, 0x7f, - 0xb2, 0x00, 0xb5, 0xb6, 0x6e, 0x1c, 0xb8, 0x7b, 0x7b, 0xe4, 0x1d, 0xa8, 0x5b, 0x4e, 0x40, 0xbd, - 0x43, 0xdd, 0x96, 0xb0, 0x0b, 0x31, 0xd8, 0x70, 0x43, 0x18, 0x3d, 0x1e, 0xdb, 0x7d, 0x31, 0x41, - 0xab, 0x03, 0xb9, 0x6b, 0xe1, 0x9a, 0xf1, 0xba, 0xc4, 0xc0, 0x10, 0x8d, 0xb4, 0xa0, 0xe2, 0x07, - 0xb4, 0xef, 0xf3, 0x35, 0x70, 0x5a, 0x34, 0xa3, 0xc3, 0x0a, 0x50, 0x94, 0x6b, 0x7f, 0xa5, 0x00, - 0x8d, 0xb6, 0xee, 0x5b, 0x06, 0x7b, 0x4a, 0xb2, 0x02, 0xe5, 0x81, 0x4f, 0xbd, 0xb3, 0x3d, 0x1b, - 0x5f, 0xb6, 0x76, 0x7c, 0xea, 0x21, 0xaf, 0x4c, 0xee, 0x41, 0xbd, 0xaf, 0xfb, 0xfe, 0x03, 0xd7, - 0x33, 0xe5, 0xd2, 0x7b, 0x4a, 0x20, 0xb1, 0x4d, 0x90, 0x55, 0x31, 0x04, 0xd1, 0x9a, 0x10, 0xe9, - 0x1e, 0xda, 0xef, 0x16, 0xe0, 0x62, 0x7b, 0xb0, 0xb7, 0x47, 0x3d, 0xa9, 0x15, 0x4b, 0x7d, 0x93, - 0x42, 0xc5, 0xa3, 0xa6, 0xe5, 0xcb, 0xb6, 0xaf, 0x8e, 0x3d, 0x50, 0x90, 0xa1, 0x48, 0xf5, 0x96, - 0xf7, 0x17, 0x2f, 0x40, 0x81, 0x4e, 0x06, 0xd0, 0x78, 0x9f, 0xb2, 0xdd, 0x38, 0xd5, 0x7b, 0xf2, - 0xe9, 0x6e, 0x8f, 0x2d, 0xea, 0x2d, 0x1a, 0x74, 0x38, 0x52, 0x5c, 0x9b, 0x0e, 0x0b, 0x31, 0x92, - 0xa4, 0xfd, 0x76, 0x05, 0xa6, 0x56, 0xdc, 0xde, 0xae, 0xe5, 0x50, 0xf3, 0xa6, 0xd9, 0xa5, 0xe4, - 0x3d, 0x28, 0x53, 0xb3, 0x4b, 0xe5, 0xd3, 0x8e, 0xaf, 0x78, 0x30, 0xb0, 0x48, 0x7d, 0x62, 0xff, - 0x90, 0x03, 0x93, 0x0d, 0x98, 0xd9, 0xf3, 0xdc, 0x9e, 0x98, 0xcb, 0xb7, 0x8f, 0xfa, 0x52, 0x77, - 0x6e, 0xff, 0x98, 0xfa, 0x70, 0xd6, 0x12, 0xd4, 0x47, 0xc7, 0x2d, 0x88, 0xfe, 0x61, 0xaa, 0x2e, - 0x79, 0x07, 0xe6, 0xa2, 0x92, 0x70, 0x52, 0x5b, 0x61, 0xdb, 0x19, 0xae, 0x3b, 0x55, 0xda, 0x57, - 0x4f, 0x8e, 0x5b, 0x73, 0x6b, 0x23, 0x78, 0x70, 0x64, 0x6d, 0x36, 0x55, 0xcc, 0x46, 0x44, 0xb1, - 0xd0, 0x48, 0x95, 0x69, 0x42, 0x2b, 0x18, 0xdf, 0xf7, 0xad, 0xa5, 0x44, 0xe0, 0x90, 0x50, 0xb2, - 0x06, 0x53, 0x81, 0x1b, 0xeb, 0xaf, 0x0a, 0xef, 0x2f, 0x4d, 0x19, 0x2a, 0xb6, 0xdd, 0x91, 0xbd, - 0x95, 0xa8, 0x47, 0x10, 0x2e, 0xab, 0xff, 0xa9, 0x9e, 0xaa, 0xf2, 0x9e, 0x9a, 0x3f, 0x39, 0x6e, - 0x5d, 0xde, 0xce, 0xe4, 0xc0, 0x11, 0x35, 0xc9, 0x2f, 0x17, 0x60, 0x46, 0x91, 0x64, 0x1f, 0xd5, - 0x26, 0xd9, 0x47, 0x84, 0x8d, 0x88, 0xed, 0x84, 0x00, 0x4c, 0x09, 0xd4, 0xbe, 0x5f, 0x83, 0x46, - 0x38, 0xd5, 0x93, 0xcf, 0x41, 0x85, 0x9b, 0x20, 0xa4, 0x06, 0x1f, 0xae, 0xe1, 0xdc, 0x52, 0x81, - 0x82, 0x46, 0x3e, 0x0f, 0x35, 0xc3, 0xed, 0xf5, 0x74, 0xc7, 0xe4, 0x66, 0xa5, 0x46, 0xbb, 0xc9, - 0x54, 0x97, 0x15, 0x51, 0x84, 0x8a, 0x46, 0xae, 0x42, 0x59, 0xf7, 0xba, 0xc2, 0xc2, 0xd3, 0x10, - 0xf3, 0xd1, 0xb2, 0xd7, 0xf5, 0x91, 0x97, 0x92, 0xaf, 0x40, 0x89, 0x3a, 0x87, 0x73, 0xe5, 0xd1, - 0xba, 0xd1, 0x4d, 0xe7, 0xf0, 0xbe, 0xee, 0xb5, 0x9b, 0xb2, 0x0d, 0xa5, 0x9b, 0xce, 0x21, 0xb2, - 0x3a, 0x64, 0x03, 0x6a, 0xd4, 0x39, 0x64, 0xef, 0x5e, 0x9a, 0x5e, 0x3e, 0x3b, 0xa2, 0x3a, 0x63, - 0x91, 0xdb, 0x84, 0x50, 0xc3, 0x92, 0xc5, 0xa8, 0x20, 0xc8, 0x37, 0x60, 0x4a, 0x28, 0x5b, 0x9b, - 0xec, 0x9d, 0xf8, 0x73, 0x55, 0x0e, 0xd9, 0x1a, 0xad, 0xad, 0x71, 0xbe, 0xc8, 0xd4, 0x15, 0x2b, - 0xf4, 0x31, 0x01, 0x45, 0xbe, 0x01, 0x0d, 0xb5, 0x33, 0x56, 0x6f, 0x36, 0xd3, 0x4a, 0xa4, 0xb6, - 0xd3, 0x48, 0xbf, 0x35, 0xb0, 0x3c, 0xda, 0xa3, 0x4e, 0xe0, 0xb7, 0x9f, 0x57, 0x76, 0x03, 0x45, - 0xf5, 0x31, 0x42, 0x23, 0xbb, 0xc3, 0xe6, 0x2e, 0x61, 0xab, 0xf9, 0xdc, 0x88, 0x59, 0x7d, 0x0c, - 0x5b, 0xd7, 0x37, 0xe1, 0x42, 0x68, 0x8f, 0x92, 0x26, 0x0d, 0x61, 0xbd, 0xf9, 0x12, 0xab, 0xbe, - 0x9e, 0x24, 0x3d, 0x3a, 0x6e, 0xbd, 0x94, 0x61, 0xd4, 0x88, 0x18, 0x30, 0x0d, 0x46, 0x3e, 0x80, - 0x19, 0x8f, 0xea, 0xa6, 0xe5, 0x50, 0xdf, 0xdf, 0xf2, 0xdc, 0xdd, 0xfc, 0x9a, 0x27, 0x47, 0x11, - 0xc3, 0x1e, 0x13, 0xc8, 0x98, 0x92, 0x44, 0x1e, 0xc0, 0xb4, 0x6d, 0x1d, 0xd2, 0x48, 0x74, 0x73, - 0x22, 0xa2, 0x9f, 0x3f, 0x39, 0x6e, 0x4d, 0x6f, 0xc4, 0x81, 0x31, 0x29, 0x87, 0x69, 0x2a, 0x7d, - 0xd7, 0x0b, 0x94, 0x7a, 0xfa, 0xd9, 0xc7, 0xaa, 0xa7, 0x5b, 0xae, 0x17, 0x44, 0x1f, 0x21, 0xfb, - 0xe7, 0xa3, 0xa8, 0xae, 0xfd, 0x8d, 0x0a, 0x0c, 0x6f, 0xe2, 0x92, 0x23, 0xae, 0x30, 0xe9, 0x11, - 0x97, 0x1e, 0x0d, 0x62, 0xed, 0x79, 0x5d, 0x56, 0x9b, 0xc0, 0x88, 0xc8, 0x18, 0xd5, 0xa5, 0x49, - 0x8f, 0xea, 0x67, 0x66, 0xe2, 0x19, 0x1e, 0xfe, 0xd5, 0x8f, 0x6f, 0xf8, 0xd7, 0x9e, 0xce, 0xf0, - 0xd7, 0xbe, 0x57, 0x86, 0x99, 0x55, 0x9d, 0xf6, 0x5c, 0xe7, 0x89, 0xfb, 0xf8, 0xc2, 0x33, 0xb1, - 0x8f, 0xbf, 0x01, 0x75, 0x8f, 0xf6, 0x6d, 0xcb, 0xd0, 0x85, 0xba, 0x2e, 0xed, 0xe6, 0x28, 0xcb, - 0x30, 0xa4, 0x8e, 0xb0, 0xdf, 0x94, 0x9e, 0x49, 0xfb, 0x4d, 0xf9, 0xe3, 0xb7, 0xdf, 0x68, 0xbf, - 0x5c, 0x04, 0xae, 0xda, 0x92, 0xeb, 0x50, 0x66, 0x6a, 0x5b, 0xda, 0x6a, 0xc8, 0xbf, 0x16, 0x4e, - 0x21, 0xf3, 0x50, 0x0c, 0x5c, 0x39, 0xdd, 0x80, 0xa4, 0x17, 0xb7, 0x5d, 0x2c, 0x06, 0x2e, 0xf9, - 0x00, 0xc0, 0x70, 0x1d, 0xd3, 0x52, 0xee, 0xa4, 0x7c, 0x0f, 0xb6, 0xe6, 0x7a, 0x0f, 0x74, 0xcf, - 0x5c, 0x09, 0x11, 0xc5, 0x0e, 0x3e, 0xfa, 0x8f, 0x31, 0x69, 0xe4, 0x0d, 0xa8, 0xba, 0xce, 0xda, - 0xc0, 0xb6, 0x79, 0x87, 0x36, 0xda, 0x5f, 0x38, 0x39, 0x6e, 0x55, 0xef, 0xf1, 0x92, 0x47, 0xc7, - 0xad, 0x2b, 0x62, 0x47, 0xc4, 0xfe, 0xbd, 0xed, 0x59, 0x81, 0xe5, 0x74, 0xc3, 0x0d, 0xad, 0xac, - 0xa6, 0xfd, 0x4a, 0x01, 0x9a, 0x6b, 0xd6, 0x43, 0x6a, 0xbe, 0x6d, 0x39, 0xa6, 0xfb, 0x80, 0x20, - 0x54, 0x6d, 0xea, 0x74, 0x83, 0xfd, 0x31, 0x77, 0x9c, 0xc2, 0xae, 0xc3, 0x11, 0x50, 0x22, 0x91, - 0x45, 0x68, 0x88, 0xfd, 0x8a, 0xe5, 0x74, 0x79, 0x1f, 0xd6, 0xa3, 0x99, 0xbe, 0xa3, 0x08, 0x18, - 0xf1, 0x68, 0x47, 0xf0, 0xfc, 0x50, 0x37, 0x10, 0x13, 0xca, 0x81, 0xde, 0x55, 0x8b, 0xca, 0xda, - 0xd8, 0x1d, 0xbc, 0xad, 0x77, 0x63, 0x9d, 0xcb, 0xb5, 0xc2, 0x6d, 0x9d, 0x69, 0x85, 0x0c, 0x5d, - 0xfb, 0x3f, 0x05, 0xa8, 0xaf, 0x0d, 0x1c, 0x83, 0x6f, 0xea, 0x9f, 0x6c, 0x4d, 0x56, 0x2a, 0x66, - 0x31, 0x53, 0xc5, 0x1c, 0x40, 0xf5, 0xe0, 0x41, 0xa8, 0x82, 0x36, 0x97, 0x36, 0xc7, 0x1f, 0x15, - 0xb2, 0x49, 0x0b, 0x77, 0x38, 0x9e, 0x70, 0x76, 0xce, 0xc8, 0x06, 0x55, 0xef, 0xbc, 0xcd, 0x85, - 0x4a, 0x61, 0xf3, 0x5f, 0x81, 0x66, 0x8c, 0xed, 0x4c, 0x7e, 0x8f, 0xbf, 0x59, 0x86, 0xea, 0xad, - 0x4e, 0x67, 0x79, 0x6b, 0x9d, 0xbc, 0x0a, 0x4d, 0xe9, 0x07, 0xbb, 0x1b, 0xf5, 0x41, 0xe8, 0x06, - 0xed, 0x44, 0x24, 0x8c, 0xf3, 0x31, 0x05, 0xde, 0xa3, 0xba, 0xdd, 0x93, 0x1f, 0x4b, 0xa8, 0x3b, - 0x20, 0x2b, 0x44, 0x41, 0x23, 0x3a, 0xcc, 0x0c, 0x7c, 0xea, 0xb1, 0x2e, 0x14, 0xfb, 0x7d, 0xf9, - 0xd9, 0x9c, 0xd2, 0x22, 0xc0, 0x17, 0x98, 0x9d, 0x04, 0x00, 0xa6, 0x00, 0xc9, 0xeb, 0x50, 0xd7, - 0x07, 0xc1, 0x3e, 0xdf, 0x72, 0x89, 0x6f, 0xe3, 0x2a, 0x77, 0x13, 0xca, 0xb2, 0x47, 0xc7, 0xad, - 0xa9, 0x3b, 0xd8, 0x7e, 0x55, 0xfd, 0xc7, 0x90, 0x9b, 0x35, 0x4e, 0xd9, 0x18, 0x64, 0xe3, 0x2a, - 0x67, 0x6e, 0xdc, 0x56, 0x02, 0x00, 0x53, 0x80, 0xe4, 0x5d, 0x98, 0x3a, 0xa0, 0x47, 0x81, 0xbe, - 0x2b, 0x05, 0x54, 0xcf, 0x22, 0x60, 0x96, 0x29, 0xfd, 0x77, 0x62, 0xd5, 0x31, 0x01, 0x46, 0x7c, - 0xb8, 0x74, 0x40, 0xbd, 0x5d, 0xea, 0xb9, 0xd2, 0x5e, 0x21, 0x85, 0xd4, 0xce, 0x22, 0x64, 0xee, - 0xe4, 0xb8, 0x75, 0xe9, 0x4e, 0x06, 0x0c, 0x66, 0x82, 0x6b, 0xff, 0xbb, 0x08, 0x17, 0x6e, 0x89, - 0x40, 0x04, 0xd7, 0x13, 0x9a, 0x07, 0xb9, 0x02, 0x25, 0xaf, 0x3f, 0xe0, 0x23, 0xa7, 0x24, 0x5c, - 0x0d, 0xb8, 0xb5, 0x83, 0xac, 0x8c, 0xbc, 0x03, 0x75, 0x53, 0x4e, 0x19, 0xd2, 0x5c, 0x32, 0x96, - 0x69, 0x4b, 0xfd, 0xc3, 0x10, 0x8d, 0xed, 0x0d, 0x7b, 0x7e, 0xb7, 0x63, 0x7d, 0x40, 0xa5, 0x05, - 0x81, 0xef, 0x0d, 0x37, 0x45, 0x11, 0x2a, 0x1a, 0x5b, 0x55, 0x0f, 0xe8, 0x91, 0xd8, 0x3f, 0x97, - 0xa3, 0x55, 0xf5, 0x8e, 0x2c, 0xc3, 0x90, 0x4a, 0x5a, 0xea, 0x63, 0x61, 0xa3, 0xa0, 0x2c, 0x6c, - 0x3f, 0xf7, 0x59, 0x81, 0xfc, 0x6e, 0xd8, 0x94, 0xf9, 0xbe, 0x15, 0x04, 0xd4, 0x93, 0xaf, 0x71, - 0xac, 0x29, 0xf3, 0x2d, 0x8e, 0x80, 0x12, 0x89, 0xfc, 0x04, 0x34, 0x38, 0x78, 0xdb, 0x76, 0x77, - 0xf9, 0x8b, 0x6b, 0x08, 0x2b, 0xd0, 0x7d, 0x55, 0x88, 0x11, 0x5d, 0xfb, 0x83, 0x22, 0x5c, 0xbe, - 0x45, 0x03, 0xa1, 0xd5, 0xac, 0xd2, 0xbe, 0xed, 0x1e, 0x31, 0x7d, 0x1a, 0xe9, 0xb7, 0xc8, 0x9b, - 0x00, 0x96, 0xbf, 0xdb, 0x39, 0x34, 0xf8, 0x77, 0x20, 0xbe, 0xe1, 0xeb, 0xf2, 0x93, 0x84, 0xf5, - 0x4e, 0x5b, 0x52, 0x1e, 0x25, 0xfe, 0x61, 0xac, 0x4e, 0xb4, 0x21, 0x2f, 0x3e, 0x66, 0x43, 0xde, - 0x01, 0xe8, 0x47, 0x5a, 0x79, 0x89, 0x73, 0xfe, 0x94, 0x12, 0x73, 0x16, 0x85, 0x3c, 0x06, 0x93, - 0x47, 0x4f, 0x76, 0x60, 0xd6, 0xa4, 0x7b, 0xfa, 0xc0, 0x0e, 0xc2, 0x9d, 0x84, 0xfc, 0x88, 0x4f, - 0xbf, 0x19, 0x09, 0x83, 0x24, 0x56, 0x53, 0x48, 0x38, 0x84, 0xad, 0xfd, 0xad, 0x12, 0xcc, 0xdf, - 0xa2, 0x41, 0x68, 0xa3, 0x93, 0xb3, 0x63, 0xa7, 0x4f, 0x0d, 0xf6, 0x16, 0x3e, 0x2c, 0x40, 0xd5, - 0xd6, 0x77, 0xa9, 0xcd, 0x56, 0x2f, 0xf6, 0x34, 0xef, 0x8d, 0xbd, 0x10, 0x8c, 0x96, 0xb2, 0xb0, - 0xc1, 0x25, 0xa4, 0x96, 0x06, 0x51, 0x88, 0x52, 0x3c, 0x9b, 0xd4, 0x0d, 0x7b, 0xe0, 0x07, 0x62, - 0x67, 0x27, 0xf5, 0xc9, 0x70, 0x52, 0x5f, 0x89, 0x48, 0x18, 0xe7, 0x23, 0x4b, 0x00, 0x86, 0x6d, - 0x51, 0x27, 0xe0, 0xb5, 0xc4, 0x77, 0x45, 0xd4, 0xfb, 0x5d, 0x09, 0x29, 0x18, 0xe3, 0x62, 0xa2, - 0x7a, 0xae, 0x63, 0x05, 0xae, 0x10, 0x55, 0x4e, 0x8a, 0xda, 0x8c, 0x48, 0x18, 0xe7, 0xe3, 0xd5, - 0x68, 0xe0, 0x59, 0x86, 0xcf, 0xab, 0x55, 0x52, 0xd5, 0x22, 0x12, 0xc6, 0xf9, 0xd8, 0x9a, 0x17, - 0x7b, 0xfe, 0x33, 0xad, 0x79, 0xbf, 0xd1, 0x80, 0x6b, 0x89, 0x6e, 0x0d, 0xf4, 0x80, 0xee, 0x0d, - 0xec, 0x0e, 0x0d, 0xd4, 0x0b, 0x1c, 0x73, 0x2d, 0xfc, 0x33, 0xd1, 0x7b, 0x17, 0xe1, 0x4f, 0xc6, - 0x64, 0xde, 0xfb, 0x50, 0x03, 0x4f, 0xf5, 0xee, 0x17, 0xa1, 0xe1, 0xe8, 0x81, 0xcf, 0x3f, 0x5c, - 0xf9, 0x8d, 0x86, 0x6a, 0xd8, 0x5d, 0x45, 0xc0, 0x88, 0x87, 0x6c, 0xc1, 0x25, 0xd9, 0xc5, 0x37, - 0x1f, 0xb2, 0x3d, 0x3f, 0xf5, 0x44, 0x5d, 0xb9, 0x9c, 0xca, 0xba, 0x97, 0x36, 0x33, 0x78, 0x30, - 0xb3, 0x26, 0xd9, 0x84, 0x8b, 0x86, 0x08, 0x09, 0xa1, 0xb6, 0xab, 0x9b, 0x0a, 0x50, 0x98, 0x44, - 0xc3, 0xad, 0xd1, 0xca, 0x30, 0x0b, 0x66, 0xd5, 0x4b, 0x8f, 0xe6, 0xea, 0x58, 0xa3, 0xb9, 0x36, - 0xce, 0x68, 0xae, 0x8f, 0x37, 0x9a, 0x1b, 0xa7, 0x1b, 0xcd, 0xac, 0xe7, 0xd9, 0x38, 0xa2, 0x1e, - 0x53, 0x4f, 0xc4, 0x0a, 0x1b, 0x8b, 0x38, 0x0a, 0x7b, 0xbe, 0x93, 0xc1, 0x83, 0x99, 0x35, 0xc9, - 0x2e, 0xcc, 0x8b, 0xf2, 0x9b, 0x8e, 0xe1, 0x1d, 0xf5, 0xd9, 0xc2, 0x13, 0xc3, 0x6d, 0x26, 0x6c, - 0xd2, 0xf3, 0x9d, 0x91, 0x9c, 0xf8, 0x18, 0x14, 0xf2, 0x33, 0x30, 0x2d, 0xde, 0xd2, 0xa6, 0xde, - 0xe7, 0xb0, 0x22, 0xfe, 0xe8, 0x05, 0x09, 0x3b, 0xbd, 0x12, 0x27, 0x62, 0x92, 0x97, 0x2c, 0xc3, - 0x85, 0xfe, 0xa1, 0xc1, 0x7e, 0xae, 0xef, 0xdd, 0xa5, 0xd4, 0xa4, 0x26, 0x77, 0x78, 0x36, 0xda, - 0x2f, 0x2a, 0xeb, 0xce, 0x56, 0x92, 0x8c, 0x69, 0x7e, 0xf2, 0x3a, 0x4c, 0xf9, 0x81, 0xee, 0x05, - 0xd2, 0x10, 0x3c, 0x37, 0x23, 0xe2, 0xb3, 0x94, 0x9d, 0xb4, 0x13, 0xa3, 0x61, 0x82, 0x33, 0x73, - 0xbd, 0xb8, 0x70, 0x7e, 0xeb, 0x45, 0x9e, 0xd9, 0xea, 0x1f, 0x15, 0xe1, 0xfa, 0x2d, 0x1a, 0x6c, - 0xba, 0x8e, 0x34, 0xa3, 0x67, 0x2d, 0xfb, 0xa7, 0xb2, 0xa2, 0x27, 0x17, 0xed, 0xe2, 0x44, 0x17, - 0xed, 0xd2, 0x84, 0x16, 0xed, 0xf2, 0x39, 0x2e, 0xda, 0x7f, 0xa7, 0x08, 0x2f, 0x26, 0x7a, 0x72, - 0xcb, 0x35, 0xd5, 0x84, 0xff, 0x69, 0x07, 0x9e, 0xa2, 0x03, 0x1f, 0x09, 0xbd, 0x93, 0x3b, 0x42, - 0x53, 0x1a, 0xcf, 0x77, 0xd3, 0x1a, 0xcf, 0xbb, 0x79, 0x56, 0xbe, 0x0c, 0x09, 0xa7, 0x5a, 0xf1, - 0xde, 0x02, 0xe2, 0x49, 0xb7, 0x6d, 0x64, 0xce, 0x96, 0x4a, 0x4f, 0x18, 0x00, 0x8a, 0x43, 0x1c, - 0x98, 0x51, 0x8b, 0x74, 0xe0, 0x05, 0x9f, 0x3a, 0x81, 0xe5, 0x50, 0x3b, 0x09, 0x27, 0xb4, 0xa1, - 0x97, 0x24, 0xdc, 0x0b, 0x9d, 0x2c, 0x26, 0xcc, 0xae, 0x9b, 0x67, 0x1e, 0xf8, 0x67, 0xc0, 0x55, - 0x4e, 0xd1, 0x35, 0x13, 0xd3, 0x58, 0x3e, 0x4c, 0x6b, 0x2c, 0xef, 0xe5, 0x7f, 0x6f, 0xe3, 0x69, - 0x2b, 0x4b, 0x00, 0xfc, 0x2d, 0xc4, 0xd5, 0x95, 0x70, 0x91, 0xc6, 0x90, 0x82, 0x31, 0x2e, 0xb6, - 0x00, 0xa9, 0x7e, 0x8e, 0x6b, 0x2a, 0xe1, 0x02, 0xd4, 0x89, 0x13, 0x31, 0xc9, 0x3b, 0x52, 0xdb, - 0xa9, 0x8c, 0xad, 0xed, 0xbc, 0x05, 0x24, 0x61, 0x78, 0x14, 0x78, 0xd5, 0x64, 0xfc, 0xf1, 0xfa, - 0x10, 0x07, 0x66, 0xd4, 0x1a, 0x31, 0x94, 0x6b, 0x93, 0x1d, 0xca, 0xf5, 0xf1, 0x87, 0x32, 0x79, - 0x0f, 0xae, 0x70, 0x51, 0xb2, 0x7f, 0x92, 0xc0, 0x42, 0xef, 0xf9, 0xac, 0x04, 0xbe, 0x82, 0xa3, - 0x18, 0x71, 0x34, 0x06, 0x7b, 0x3f, 0x86, 0x47, 0x4d, 0x26, 0x5c, 0xb7, 0x47, 0xeb, 0x44, 0x2b, - 0x19, 0x3c, 0x98, 0x59, 0x93, 0x0d, 0xb1, 0x80, 0x0d, 0x43, 0x7d, 0xd7, 0xa6, 0xa6, 0x8c, 0xbf, - 0x0e, 0x87, 0xd8, 0xf6, 0x46, 0x47, 0x52, 0x30, 0xc6, 0x95, 0xa5, 0xa6, 0x4c, 0x9d, 0x51, 0x4d, - 0xb9, 0xc5, 0xad, 0xf4, 0x7b, 0x09, 0x6d, 0x48, 0xea, 0x3a, 0x61, 0x44, 0xfd, 0x4a, 0x9a, 0x01, - 0x87, 0xeb, 0x70, 0x2d, 0xd1, 0xf0, 0xac, 0x7e, 0xe0, 0x27, 0xb1, 0x66, 0x52, 0x5a, 0x62, 0x06, - 0x0f, 0x66, 0xd6, 0x64, 0xfa, 0xf9, 0x3e, 0xd5, 0xed, 0x60, 0x3f, 0x09, 0x78, 0x21, 0xa9, 0x9f, - 0xdf, 0x1e, 0x66, 0xc1, 0xac, 0x7a, 0x99, 0x0b, 0xd2, 0xec, 0xb3, 0xa9, 0x56, 0x7d, 0xa7, 0x04, - 0x57, 0x6e, 0xd1, 0x20, 0x0c, 0x4d, 0xfb, 0xd4, 0x8c, 0xf2, 0x31, 0x98, 0x51, 0x7e, 0xbd, 0x02, - 0x17, 0x6f, 0xd1, 0x60, 0x48, 0x1b, 0xfb, 0xff, 0xb4, 0xfb, 0x37, 0xe1, 0x62, 0x14, 0x0d, 0xd9, - 0x09, 0x5c, 0x4f, 0xac, 0xe5, 0xa9, 0xdd, 0x72, 0x67, 0x98, 0x05, 0xb3, 0xea, 0x91, 0x6f, 0xc0, - 0x8b, 0x7c, 0xa9, 0x77, 0xba, 0xc2, 0x3e, 0x2b, 0x8c, 0x09, 0xb1, 0xf3, 0x3c, 0x2d, 0x09, 0xf9, - 0x62, 0x27, 0x9b, 0x0d, 0x47, 0xd5, 0x27, 0xdf, 0x86, 0xa9, 0xbe, 0xd5, 0xa7, 0xb6, 0xe5, 0x70, - 0xfd, 0x2c, 0x77, 0x10, 0xd1, 0x56, 0x0c, 0x2c, 0xda, 0xc0, 0xc5, 0x4b, 0x31, 0x21, 0x30, 0x73, - 0xa4, 0xd6, 0xcf, 0x71, 0xa4, 0xfe, 0x8f, 0x22, 0xd4, 0x6e, 0x79, 0xee, 0xa0, 0xdf, 0x3e, 0x22, - 0x5d, 0xa8, 0x3e, 0xe0, 0xce, 0x33, 0xe9, 0x9a, 0x1a, 0xff, 0x44, 0x81, 0xf0, 0xc1, 0x45, 0x2a, - 0x91, 0xf8, 0x8f, 0x12, 0x9e, 0x0d, 0xe2, 0x03, 0x7a, 0x44, 0x4d, 0xe9, 0x43, 0x0b, 0x07, 0xf1, - 0x1d, 0x56, 0x88, 0x82, 0x46, 0x7a, 0x70, 0x41, 0xb7, 0x6d, 0xf7, 0x01, 0x35, 0x37, 0xf4, 0x80, - 0xfb, 0xbd, 0xa5, 0x6f, 0xe5, 0xac, 0x66, 0x69, 0x1e, 0xcc, 0xb0, 0x9c, 0x84, 0xc2, 0x34, 0x36, - 0x79, 0x1f, 0x6a, 0x7e, 0xe0, 0x7a, 0x4a, 0xd9, 0x6a, 0x2e, 0xad, 0x8c, 0xff, 0xd2, 0xdb, 0x5f, - 0xef, 0x08, 0x28, 0x61, 0xb3, 0x97, 0x7f, 0x50, 0x09, 0xd0, 0x7e, 0xad, 0x00, 0x70, 0x7b, 0x7b, - 0x7b, 0x4b, 0xba, 0x17, 0x4c, 0x28, 0xeb, 0x83, 0xd0, 0x51, 0x39, 0xbe, 0x43, 0x30, 0x11, 0xc8, - 0x2b, 0x7d, 0x78, 0x83, 0x60, 0x1f, 0x39, 0x3a, 0xf9, 0x71, 0xa8, 0x49, 0x05, 0x59, 0x76, 0x7b, - 0x18, 0x4f, 0x21, 0x95, 0x68, 0x54, 0x74, 0xed, 0xb7, 0x8a, 0x00, 0xeb, 0xa6, 0x4d, 0x3b, 0xea, - 0x10, 0x48, 0x23, 0xd8, 0xf7, 0xa8, 0xbf, 0xef, 0xda, 0xe6, 0x98, 0xde, 0x54, 0x6e, 0xf3, 0xdf, - 0x56, 0x20, 0x18, 0xe1, 0x11, 0x13, 0xa6, 0xfc, 0x80, 0xf6, 0x55, 0x6c, 0xef, 0x98, 0x4e, 0x94, - 0x59, 0x61, 0x17, 0x89, 0x70, 0x30, 0x81, 0x4a, 0x74, 0x68, 0x5a, 0x8e, 0x21, 0x3e, 0x90, 0xf6, - 0xd1, 0x98, 0x03, 0xe9, 0x02, 0xdb, 0x71, 0xac, 0x47, 0x30, 0x18, 0xc7, 0xd4, 0x7e, 0xa7, 0x08, - 0x97, 0xb9, 0x3c, 0xd6, 0x8c, 0x44, 0x04, 0x2f, 0xf9, 0xe3, 0x43, 0x07, 0x56, 0xff, 0xe8, 0xe9, - 0x44, 0x8b, 0xf3, 0x8e, 0x9b, 0x34, 0xd0, 0x23, 0x7d, 0x2e, 0x2a, 0x8b, 0x9d, 0x52, 0x1d, 0x40, - 0xd9, 0x67, 0xf3, 0x95, 0xe8, 0xbd, 0xce, 0xd8, 0x43, 0x28, 0xfb, 0x01, 0xf8, 0xec, 0x15, 0x7a, - 0x8d, 0xf9, 0xac, 0xc5, 0xc5, 0x91, 0x5f, 0x84, 0xaa, 0x1f, 0xe8, 0xc1, 0x40, 0x7d, 0x9a, 0x3b, - 0x93, 0x16, 0xcc, 0xc1, 0xa3, 0x79, 0x44, 0xfc, 0x47, 0x29, 0x54, 0xfb, 0x9d, 0x02, 0xcc, 0x67, - 0x57, 0xdc, 0xb0, 0xfc, 0x80, 0xfc, 0xb1, 0xa1, 0x6e, 0x3f, 0xe5, 0x1b, 0x67, 0xb5, 0x79, 0xa7, - 0x87, 0x67, 0x1a, 0x54, 0x49, 0xac, 0xcb, 0x03, 0xa8, 0x58, 0x01, 0xed, 0xa9, 0xfd, 0xe5, 0xbd, - 0x09, 0x3f, 0x7a, 0x6c, 0x69, 0x67, 0x52, 0x50, 0x08, 0xd3, 0xbe, 0x57, 0x1c, 0xf5, 0xc8, 0x7c, - 0xf9, 0xb0, 0x93, 0x51, 0xe2, 0x77, 0xf2, 0x45, 0x89, 0x27, 0x1b, 0x34, 0x1c, 0x2c, 0xfe, 0x27, - 0x86, 0x83, 0xc5, 0xef, 0xe5, 0x0f, 0x16, 0x4f, 0x75, 0xc3, 0xc8, 0x98, 0xf1, 0x8f, 0x4a, 0x70, - 0xf5, 0x71, 0xc3, 0x86, 0xad, 0x67, 0x72, 0x74, 0xe6, 0x5d, 0xcf, 0x1e, 0x3f, 0x0e, 0xc9, 0x12, - 0x54, 0xfa, 0xfb, 0xba, 0xaf, 0x94, 0xb2, 0xab, 0x61, 0x98, 0x21, 0x2b, 0x7c, 0xc4, 0x26, 0x0d, - 0xae, 0xcc, 0xf1, 0xbf, 0x28, 0x58, 0xd9, 0x74, 0xdc, 0xa3, 0xbe, 0x1f, 0xd9, 0x04, 0xc2, 0xe9, - 0x78, 0x53, 0x14, 0xa3, 0xa2, 0x93, 0x00, 0xaa, 0xc2, 0xc4, 0x2c, 0x57, 0xa6, 0xf1, 0x03, 0xb9, - 0x32, 0x0e, 0x16, 0x44, 0x0f, 0x25, 0xbd, 0x15, 0x52, 0x16, 0x59, 0x80, 0x72, 0x10, 0x85, 0x79, - 0xab, 0xad, 0x79, 0x39, 0x43, 0x3f, 0xe5, 0x7c, 0x6c, 0x63, 0xef, 0xee, 0x72, 0xa3, 0xba, 0x29, - 0xfd, 0xe7, 0x96, 0xeb, 0x70, 0x85, 0xac, 0x14, 0x6d, 0xec, 0xef, 0x0d, 0x71, 0x60, 0x46, 0x2d, - 0xed, 0x5f, 0xd6, 0xe1, 0x72, 0xf6, 0x78, 0x60, 0xfd, 0x76, 0x48, 0x3d, 0x9f, 0x61, 0x17, 0x92, - 0xfd, 0x76, 0x5f, 0x14, 0xa3, 0xa2, 0x7f, 0xa2, 0x03, 0xce, 0x7e, 0xbd, 0x00, 0x57, 0x3c, 0xe9, - 0x23, 0x7a, 0x1a, 0x41, 0x67, 0x2f, 0x09, 0x73, 0xc6, 0x08, 0x81, 0x38, 0xba, 0x2d, 0xe4, 0xaf, - 0x15, 0x60, 0xae, 0x97, 0xb2, 0x73, 0x9c, 0xe3, 0x99, 0x4b, 0x7e, 0x8e, 0x62, 0x73, 0x84, 0x3c, - 0x1c, 0xd9, 0x12, 0xf2, 0x6d, 0x68, 0xf6, 0xd9, 0xb8, 0xf0, 0x03, 0xea, 0x18, 0x2a, 0x40, 0x74, - 0xfc, 0x2f, 0x69, 0x2b, 0xc2, 0x0a, 0xcf, 0x5c, 0x71, 0xfd, 0x20, 0x46, 0xc0, 0xb8, 0xc4, 0x67, - 0xfc, 0x90, 0xe5, 0x0d, 0xa8, 0xfb, 0x34, 0x08, 0x2c, 0xa7, 0x2b, 0xf6, 0x1b, 0x0d, 0xf1, 0xad, - 0x74, 0x64, 0x19, 0x86, 0x54, 0xf2, 0x13, 0xd0, 0xe0, 0x2e, 0xa7, 0x65, 0xaf, 0xeb, 0xcf, 0x35, - 0x78, 0xb8, 0xd8, 0xb4, 0x08, 0x80, 0x93, 0x85, 0x18, 0xd1, 0xc9, 0x97, 0x60, 0x6a, 0x97, 0x7f, - 0xbe, 0xf2, 0xdc, 0xbd, 0xb0, 0x71, 0x71, 0x6d, 0xad, 0x1d, 0x2b, 0xc7, 0x04, 0x17, 0x59, 0x02, - 0xa0, 0xa1, 0x5f, 0x2e, 0x6d, 0xcf, 0x8a, 0x3c, 0x76, 0x18, 0xe3, 0x22, 0x2f, 0x41, 0x29, 0xb0, - 0x7d, 0x6e, 0xc3, 0xaa, 0x47, 0x5b, 0xd0, 0xed, 0x8d, 0x0e, 0xb2, 0x72, 0xed, 0x0f, 0x0a, 0x70, - 0x21, 0x75, 0x1c, 0x89, 0x55, 0x19, 0x78, 0xb6, 0x9c, 0x46, 0xc2, 0x2a, 0x3b, 0xb8, 0x81, 0xac, - 0x9c, 0xbc, 0x27, 0xd5, 0xf2, 0x62, 0xce, 0x14, 0x23, 0x77, 0xf5, 0xc0, 0x67, 0x7a, 0xf8, 0x90, - 0x46, 0xce, 0xdd, 0x7c, 0x51, 0x7b, 0xe4, 0x3a, 0x10, 0x73, 0xf3, 0x45, 0x34, 0x4c, 0x70, 0xa6, - 0x0c, 0x7e, 0xe5, 0xd3, 0x18, 0xfc, 0xb4, 0x5f, 0x29, 0xc6, 0x7a, 0x40, 0x6a, 0xf6, 0x4f, 0xe8, - 0x81, 0x97, 0xd9, 0x02, 0x1a, 0x2e, 0xee, 0x8d, 0xf8, 0xfa, 0xc7, 0x17, 0x63, 0x49, 0x25, 0x6f, - 0x8b, 0xbe, 0x2f, 0xe5, 0x3c, 0xc8, 0xbd, 0xbd, 0xd1, 0x11, 0xd1, 0x55, 0xea, 0xad, 0x85, 0xaf, - 0xa0, 0x7c, 0x4e, 0xaf, 0x40, 0xfb, 0x27, 0x25, 0x68, 0xbe, 0xe5, 0xee, 0x7e, 0x42, 0x22, 0xa8, - 0xb3, 0x97, 0xa9, 0xe2, 0xc7, 0xb8, 0x4c, 0xed, 0xc0, 0x8b, 0x41, 0x60, 0x77, 0xa8, 0xe1, 0x3a, - 0xa6, 0xbf, 0xbc, 0x17, 0x50, 0x6f, 0xcd, 0x72, 0x2c, 0x7f, 0x9f, 0x9a, 0xd2, 0x9d, 0xf4, 0x99, - 0x93, 0xe3, 0xd6, 0x8b, 0xdb, 0xdb, 0x1b, 0x59, 0x2c, 0x38, 0xaa, 0x2e, 0x9f, 0x36, 0xc4, 0xd9, - 0x51, 0x7e, 0xb6, 0x4a, 0xc6, 0xdc, 0x88, 0x69, 0x23, 0x56, 0x8e, 0x09, 0x2e, 0xed, 0xdf, 0x15, - 0xa1, 0x11, 0x26, 0x8f, 0x20, 0x9f, 0x87, 0xda, 0xae, 0xe7, 0x1e, 0x50, 0x4f, 0x78, 0xee, 0xe4, - 0xd9, 0xaa, 0xb6, 0x28, 0x42, 0x45, 0x23, 0x9f, 0x83, 0x4a, 0xe0, 0xf6, 0x2d, 0x23, 0x6d, 0x50, - 0xdb, 0x66, 0x85, 0x28, 0x68, 0xfc, 0x43, 0xe0, 0x61, 0x85, 0xfc, 0xa9, 0xea, 0xb1, 0x0f, 0x81, - 0x97, 0xa2, 0xa4, 0xaa, 0x0f, 0xa1, 0x3c, 0xf1, 0x0f, 0xe1, 0xe5, 0x50, 0x05, 0xac, 0x24, 0xbf, - 0xc4, 0x94, 0xd2, 0xf6, 0x2e, 0x94, 0x7d, 0xdd, 0xb7, 0xe5, 0xf2, 0x96, 0x23, 0x5f, 0xc3, 0x72, - 0x67, 0x43, 0xe6, 0x6b, 0x58, 0xee, 0x6c, 0x20, 0x07, 0xd5, 0x7e, 0xab, 0x04, 0x4d, 0xd1, 0xbf, - 0x62, 0xf6, 0x98, 0x64, 0x0f, 0xbf, 0xc1, 0x43, 0x2e, 0xfc, 0x41, 0x8f, 0x7a, 0xdc, 0x1c, 0x25, - 0x27, 0xc3, 0xb8, 0x1f, 0x21, 0x22, 0x86, 0x61, 0x17, 0x51, 0xd1, 0x1f, 0xee, 0xae, 0x67, 0x4b, - 0x05, 0x4f, 0x80, 0x22, 0x75, 0x5c, 0x19, 0x49, 0x19, 0x2e, 0x15, 0x77, 0x62, 0x34, 0x4c, 0x70, - 0x6a, 0xff, 0xbd, 0x08, 0x8d, 0x0d, 0x6b, 0x8f, 0x1a, 0x47, 0x86, 0x4d, 0xc9, 0x37, 0x61, 0xde, - 0xa4, 0x36, 0x65, 0x2b, 0xe6, 0x2d, 0x4f, 0x37, 0xe8, 0x16, 0xf5, 0x2c, 0x9e, 0xc0, 0x89, 0x7d, - 0x83, 0x32, 0xc0, 0xf5, 0xda, 0xc9, 0x71, 0x6b, 0x7e, 0x75, 0x24, 0x17, 0x3e, 0x06, 0x81, 0xac, - 0xc3, 0x94, 0x49, 0x7d, 0xcb, 0xa3, 0xe6, 0x56, 0x6c, 0x43, 0xf4, 0x79, 0xd5, 0xce, 0xd5, 0x18, - 0xed, 0xd1, 0x71, 0x6b, 0x5a, 0x19, 0x42, 0xc5, 0xce, 0x28, 0x51, 0x95, 0x4d, 0x2d, 0x7d, 0x7d, - 0xe0, 0xd3, 0x8c, 0x76, 0x96, 0x78, 0x3b, 0xf9, 0xd4, 0xb2, 0x95, 0xcd, 0x82, 0xa3, 0xea, 0x92, - 0x5d, 0x98, 0xe3, 0xed, 0xcf, 0xc2, 0x2d, 0x73, 0xdc, 0x97, 0x4f, 0x8e, 0x5b, 0xda, 0x2a, 0xed, - 0x7b, 0xd4, 0xd0, 0x03, 0x6a, 0xae, 0x8e, 0xe0, 0xc6, 0x91, 0x38, 0x5a, 0x05, 0x4a, 0x1b, 0x6e, - 0x57, 0xfb, 0x5e, 0x09, 0xc2, 0x8c, 0x62, 0xe4, 0x4f, 0x17, 0xa0, 0xa9, 0x3b, 0x8e, 0x1b, 0xc8, - 0x6c, 0x5d, 0x22, 0x9a, 0x00, 0x73, 0x27, 0x2e, 0x5b, 0x58, 0x8e, 0x40, 0x85, 0x23, 0x3a, 0x74, - 0x8e, 0xc7, 0x28, 0x18, 0x97, 0x4d, 0x06, 0x29, 0xdf, 0xf8, 0x66, 0xfe, 0x56, 0x9c, 0xc2, 0x13, - 0x3e, 0xff, 0x35, 0x98, 0x4d, 0x37, 0xf6, 0x2c, 0xae, 0xad, 0x5c, 0x41, 0x06, 0x45, 0x80, 0x28, - 0x3e, 0xe6, 0x29, 0x18, 0xe4, 0xac, 0x84, 0x41, 0x6e, 0xfc, 0xb4, 0x0e, 0x51, 0xa3, 0x47, 0x1a, - 0xe1, 0xbe, 0x95, 0x32, 0xc2, 0xad, 0x4f, 0x42, 0xd8, 0xe3, 0x0d, 0x6f, 0xbb, 0x70, 0x31, 0xe2, - 0x8d, 0x66, 0x97, 0x3b, 0xa9, 0xaf, 0x5f, 0xe8, 0x95, 0x5f, 0x18, 0xf1, 0xf5, 0x5f, 0x88, 0x05, - 0x2c, 0x0d, 0x7f, 0xff, 0xda, 0x5f, 0x2f, 0xc0, 0x6c, 0x5c, 0x08, 0x3f, 0x83, 0xfe, 0x65, 0x98, - 0xf6, 0xa8, 0x6e, 0xb6, 0xf5, 0xc0, 0xd8, 0xe7, 0xa1, 0xf1, 0x05, 0x1e, 0xcb, 0xce, 0x4f, 0xcb, - 0x61, 0x9c, 0x80, 0x49, 0x3e, 0xa2, 0x43, 0x93, 0x15, 0x6c, 0x5b, 0x3d, 0xea, 0x0e, 0x82, 0x31, - 0xad, 0xcc, 0x7c, 0x83, 0x87, 0x11, 0x0c, 0xc6, 0x31, 0xb5, 0x8f, 0x0a, 0x30, 0x13, 0x6f, 0xf0, - 0xb9, 0x5b, 0x20, 0xf7, 0x93, 0x16, 0xc8, 0x95, 0x09, 0xbc, 0xf7, 0x11, 0x56, 0xc7, 0xef, 0x34, - 0xe3, 0x8f, 0xc6, 0x2d, 0x8d, 0x71, 0xe3, 0x4a, 0xe1, 0xb1, 0xc6, 0x95, 0x4f, 0x7e, 0xa2, 0xaa, - 0x51, 0xbb, 0x82, 0xf2, 0x33, 0xbc, 0x2b, 0xf8, 0x38, 0xb3, 0x5d, 0xc5, 0x32, 0x36, 0x55, 0x73, - 0x64, 0x6c, 0xea, 0x85, 0x19, 0x9b, 0x6a, 0x13, 0x9b, 0xd8, 0x4e, 0x93, 0xb5, 0xa9, 0xfe, 0x54, - 0xb3, 0x36, 0x35, 0xce, 0x2b, 0x6b, 0x13, 0xe4, 0xcd, 0xda, 0xf4, 0xdd, 0x02, 0xcc, 0x98, 0x89, - 0x13, 0xc6, 0xf2, 0x6c, 0xff, 0xf8, 0xcb, 0x59, 0xf2, 0xc0, 0xb2, 0x38, 0x62, 0x96, 0x2c, 0xc3, - 0x94, 0xc8, 0xac, 0x5c, 0x49, 0x53, 0x1f, 0x4b, 0xae, 0x24, 0xf2, 0x8b, 0xd0, 0xb0, 0xd5, 0x5a, - 0x27, 0x33, 0x48, 0x6e, 0x4c, 0x64, 0x48, 0x4a, 0xcc, 0xe8, 0x14, 0x43, 0x58, 0x84, 0x91, 0x44, - 0xed, 0xf7, 0x6a, 0xf1, 0x05, 0xf1, 0x69, 0xfb, 0x38, 0x5e, 0x4b, 0xfa, 0x38, 0xae, 0xa7, 0x7d, - 0x1c, 0x43, 0xab, 0xb9, 0xf4, 0x73, 0x7c, 0x31, 0xb6, 0x4e, 0x94, 0x78, 0x92, 0xa6, 0x70, 0xc8, - 0x65, 0xac, 0x15, 0xcb, 0x70, 0x41, 0x2a, 0x01, 0x8a, 0xc8, 0x27, 0xd9, 0xe9, 0x28, 0x2a, 0x6d, - 0x35, 0x49, 0xc6, 0x34, 0x3f, 0x13, 0xe8, 0xab, 0x5c, 0xbd, 0x62, 0xc7, 0x16, 0x8d, 0x71, 0x95, - 0x47, 0x37, 0xe4, 0x60, 0xbb, 0x3b, 0x8f, 0xea, 0xbe, 0xf4, 0x54, 0xc4, 0x76, 0x77, 0xc8, 0x4b, - 0x51, 0x52, 0xe3, 0xee, 0x9a, 0xda, 0x13, 0xdc, 0x35, 0x3a, 0x34, 0x6d, 0xdd, 0x0f, 0xc4, 0x60, - 0x32, 0xe5, 0x6c, 0xf2, 0x47, 0x4e, 0xb7, 0xee, 0x33, 0x5d, 0x22, 0x52, 0xe0, 0x37, 0x22, 0x18, - 0x8c, 0x63, 0x12, 0x13, 0xa6, 0xd8, 0x5f, 0x3e, 0xb3, 0x98, 0xcb, 0x81, 0xcc, 0x68, 0x77, 0x16, - 0x19, 0xe1, 0xd6, 0x71, 0x23, 0x86, 0x83, 0x09, 0xd4, 0x11, 0x1e, 0x1d, 0x18, 0xc7, 0xa3, 0x43, - 0x7e, 0x46, 0x28, 0x6e, 0x47, 0xe1, 0x6b, 0x6d, 0xf2, 0xd7, 0x1a, 0x46, 0xb4, 0x62, 0x9c, 0x88, - 0x49, 0x5e, 0x36, 0x2a, 0x06, 0xb2, 0x1b, 0x54, 0xf5, 0xa9, 0xe4, 0xa8, 0xd8, 0x49, 0x92, 0x31, - 0xcd, 0x4f, 0xb6, 0xe0, 0x52, 0x58, 0x14, 0x6f, 0xc6, 0x34, 0xc7, 0x09, 0x43, 0x0c, 0x77, 0x32, - 0x78, 0x30, 0xb3, 0x26, 0x3f, 0xb3, 0x33, 0xf0, 0x3c, 0xea, 0x04, 0xb7, 0x75, 0x7f, 0x5f, 0xc6, - 0x2a, 0x46, 0x67, 0x76, 0x22, 0x12, 0xc6, 0xf9, 0xc8, 0x12, 0x80, 0x80, 0xe3, 0xb5, 0x2e, 0x24, - 0xc3, 0x81, 0x77, 0x42, 0x0a, 0xc6, 0xb8, 0xb4, 0xef, 0x36, 0xa0, 0x79, 0x57, 0x0f, 0xac, 0x43, - 0xca, 0xdd, 0xaf, 0xe7, 0xe3, 0x03, 0xfb, 0x4b, 0x05, 0xb8, 0x9c, 0x8c, 0xb1, 0x3d, 0x47, 0x47, - 0x18, 0xcf, 0xf1, 0x84, 0x99, 0xd2, 0x70, 0x44, 0x2b, 0xb8, 0x4b, 0x6c, 0x28, 0x64, 0xf7, 0xbc, - 0x5d, 0x62, 0x9d, 0x51, 0x02, 0x71, 0x74, 0x5b, 0x3e, 0x29, 0x2e, 0xb1, 0x67, 0x3b, 0x29, 0x69, - 0xca, 0x61, 0x57, 0x7b, 0x66, 0x1c, 0x76, 0xf5, 0x67, 0x42, 0xeb, 0xef, 0xc7, 0x1c, 0x76, 0x8d, - 0x9c, 0x81, 0x63, 0xf2, 0x58, 0x8a, 0x40, 0x1b, 0xe5, 0xf8, 0xe3, 0x19, 0x25, 0x94, 0x23, 0x85, - 0x29, 0xcb, 0xbb, 0xba, 0x6f, 0x19, 0x52, 0xed, 0xc8, 0x91, 0x84, 0x59, 0x25, 0x67, 0x14, 0xf1, - 0x25, 0xfc, 0x2f, 0x0a, 0xec, 0x28, 0x17, 0x65, 0x31, 0x57, 0x2e, 0x4a, 0xb2, 0x02, 0x65, 0xe7, - 0x80, 0x1e, 0x9d, 0x2d, 0x37, 0x03, 0xdf, 0x04, 0xde, 0xbd, 0x43, 0x8f, 0x90, 0x57, 0xd6, 0xbe, - 0x5f, 0x04, 0x60, 0x8f, 0x7f, 0x3a, 0xd7, 0xd9, 0x8f, 0x43, 0xcd, 0x1f, 0x70, 0xc3, 0x90, 0x54, - 0x98, 0xa2, 0x68, 0x3b, 0x51, 0x8c, 0x8a, 0x4e, 0x3e, 0x07, 0x95, 0x6f, 0x0d, 0xe8, 0x40, 0xc5, - 0x81, 0x84, 0xfb, 0x86, 0xaf, 0xb3, 0x42, 0x14, 0xb4, 0xf3, 0x33, 0x6f, 0x2b, 0x17, 0x5b, 0xe5, - 0xbc, 0x5c, 0x6c, 0x0d, 0xa8, 0xdd, 0x75, 0x79, 0xf0, 0xae, 0xf6, 0x5f, 0x8a, 0x00, 0x51, 0x70, - 0x24, 0xf9, 0xb5, 0x02, 0xbc, 0x10, 0x7e, 0x70, 0x81, 0xd8, 0xfe, 0xf1, 0xbc, 0xe7, 0xb9, 0xdd, - 0x6d, 0x59, 0x1f, 0x3b, 0x9f, 0x81, 0xb6, 0xb2, 0xc4, 0x61, 0x76, 0x2b, 0x08, 0x42, 0x9d, 0xf6, - 0xfa, 0xc1, 0xd1, 0xaa, 0xe5, 0xc9, 0x11, 0x98, 0x19, 0x83, 0x7b, 0x53, 0xf2, 0x88, 0xaa, 0xd2, - 0x46, 0xc1, 0x3f, 0x22, 0x45, 0xc1, 0x10, 0x87, 0xec, 0x43, 0xdd, 0x71, 0xdf, 0xf3, 0x59, 0x77, - 0xc8, 0xe1, 0xf8, 0xe6, 0xf8, 0x5d, 0x2e, 0xba, 0x55, 0xb8, 0x5d, 0xe4, 0x1f, 0xac, 0x39, 0xb2, - 0xb3, 0x7f, 0xb5, 0x08, 0x17, 0x33, 0xfa, 0x81, 0xbc, 0x09, 0xb3, 0x32, 0x0e, 0x35, 0xba, 0x00, - 0xa0, 0x10, 0x5d, 0x00, 0xd0, 0x49, 0xd1, 0x70, 0x88, 0x9b, 0xbc, 0x07, 0xa0, 0x1b, 0x06, 0xf5, - 0xfd, 0x4d, 0xd7, 0x54, 0xfb, 0x81, 0x37, 0x98, 0xfa, 0xb2, 0x1c, 0x96, 0x3e, 0x3a, 0x6e, 0xfd, - 0x64, 0x56, 0x68, 0x79, 0xaa, 0x9f, 0xa3, 0x0a, 0x18, 0x83, 0x24, 0xdf, 0x04, 0x10, 0x36, 0x80, - 0x30, 0xfb, 0xc5, 0x13, 0x0c, 0x67, 0x0b, 0x2a, 0xb9, 0xda, 0xc2, 0xd7, 0x07, 0xba, 0x13, 0x58, - 0xc1, 0x91, 0x48, 0x36, 0x74, 0x3f, 0x44, 0xc1, 0x18, 0xa2, 0xf6, 0x0f, 0x8b, 0x50, 0x57, 0xae, - 0x87, 0xa7, 0x60, 0x0b, 0xee, 0x26, 0x6c, 0xc1, 0x13, 0x0a, 0x26, 0xcf, 0xb2, 0x04, 0xbb, 0x29, - 0x4b, 0xf0, 0xad, 0xfc, 0xa2, 0x1e, 0x6f, 0x07, 0xfe, 0xcd, 0x22, 0xcc, 0x28, 0xd6, 0xbc, 0x16, - 0xda, 0xaf, 0xc2, 0x05, 0x11, 0x04, 0xb2, 0xa9, 0x3f, 0x14, 0x79, 0x97, 0x78, 0x87, 0x95, 0x45, - 0xfc, 0x76, 0x3b, 0x49, 0xc2, 0x34, 0x2f, 0x1b, 0xd6, 0xa2, 0x68, 0x87, 0x6d, 0xc2, 0x84, 0xdb, - 0x58, 0xec, 0x37, 0xf9, 0xb0, 0x6e, 0xa7, 0x68, 0x38, 0xc4, 0x9d, 0x36, 0x11, 0x97, 0xcf, 0xc1, - 0x44, 0xfc, 0xaf, 0x0b, 0x30, 0x15, 0xf5, 0xd7, 0xb9, 0x1b, 0x88, 0xf7, 0x92, 0x06, 0xe2, 0xe5, - 0xdc, 0xc3, 0x61, 0x84, 0x79, 0xf8, 0xcf, 0xd5, 0x20, 0x71, 0xa6, 0x81, 0xec, 0xc2, 0xbc, 0x95, - 0x19, 0x99, 0x19, 0x9b, 0x6d, 0xc2, 0x43, 0xfa, 0xeb, 0x23, 0x39, 0xf1, 0x31, 0x28, 0x64, 0x00, - 0xf5, 0x43, 0xea, 0x05, 0x96, 0x41, 0xd5, 0xf3, 0xdd, 0xca, 0xad, 0x92, 0x49, 0x23, 0x78, 0xd8, - 0xa7, 0xf7, 0xa5, 0x00, 0x0c, 0x45, 0x91, 0x5d, 0xa8, 0x50, 0xb3, 0x4b, 0x55, 0x26, 0xac, 0x9c, - 0x99, 0x89, 0xc3, 0xfe, 0x64, 0xff, 0x7c, 0x14, 0xd0, 0xc4, 0x8f, 0x1b, 0x9a, 0xca, 0x39, 0x15, - 0xac, 0x53, 0x9a, 0x97, 0xc8, 0x41, 0x68, 0x6d, 0xad, 0x4c, 0x68, 0xf2, 0x78, 0x8c, 0xad, 0xd5, - 0x87, 0xc6, 0x03, 0x3d, 0xa0, 0x5e, 0x4f, 0xf7, 0x0e, 0xe4, 0x6e, 0x63, 0xfc, 0x27, 0x7c, 0x5b, - 0x21, 0x45, 0x4f, 0x18, 0x16, 0x61, 0x24, 0x87, 0xb8, 0xd0, 0x08, 0xa4, 0xfa, 0xac, 0x4c, 0xca, - 0xe3, 0x0b, 0x55, 0x8a, 0xb8, 0x2f, 0xcf, 0x36, 0xa8, 0xbf, 0x18, 0xc9, 0x20, 0x87, 0x89, 0x34, - 0xf6, 0xe2, 0xf2, 0x82, 0x76, 0x0e, 0xd7, 0x84, 0x84, 0x8a, 0x96, 0x9b, 0xec, 0x74, 0xf8, 0xda, - 0xff, 0xac, 0x44, 0xd3, 0xf2, 0xd3, 0xb6, 0x13, 0x7e, 0x29, 0x69, 0x27, 0xbc, 0x96, 0xb6, 0x13, - 0xa6, 0x7c, 0xfe, 0x67, 0x8f, 0x86, 0x4e, 0x99, 0xd7, 0xca, 0xe7, 0x60, 0x5e, 0x7b, 0x05, 0x9a, - 0x87, 0x7c, 0x26, 0x10, 0x69, 0xb5, 0x2a, 0x7c, 0x19, 0xe1, 0x33, 0xfb, 0xfd, 0xa8, 0x18, 0xe3, - 0x3c, 0xac, 0x8a, 0xbc, 0xb8, 0x27, 0xcc, 0x64, 0x2d, 0xab, 0x74, 0xa2, 0x62, 0x8c, 0xf3, 0xf0, - 0x40, 0x4a, 0xcb, 0x39, 0x10, 0x15, 0x6a, 0xbc, 0x82, 0x08, 0xa4, 0x54, 0x85, 0x18, 0xd1, 0xc9, - 0x0d, 0xa8, 0x0f, 0xcc, 0x3d, 0xc1, 0x5b, 0xe7, 0xbc, 0x5c, 0xc3, 0xdc, 0x59, 0x5d, 0x93, 0x69, - 0xbe, 0x14, 0x95, 0xb5, 0xa4, 0xa7, 0xf7, 0x15, 0x81, 0xef, 0x0d, 0x65, 0x4b, 0x36, 0xa3, 0x62, - 0x8c, 0xf3, 0x90, 0x9f, 0x86, 0x19, 0x8f, 0x9a, 0x03, 0x83, 0x86, 0xb5, 0x80, 0xd7, 0x92, 0xf9, - 0x4f, 0xe3, 0x14, 0x4c, 0x71, 0x8e, 0x30, 0x12, 0x36, 0xc7, 0x32, 0x12, 0x7e, 0x0d, 0x66, 0x4c, - 0x4f, 0xb7, 0x1c, 0x6a, 0xde, 0x73, 0x78, 0x60, 0x87, 0x0c, 0xe7, 0x0c, 0x0d, 0xf4, 0xab, 0x09, - 0x2a, 0xa6, 0xb8, 0xb5, 0x7f, 0x5a, 0x84, 0x8a, 0xc8, 0xca, 0xba, 0x0e, 0x17, 0x2d, 0xc7, 0x0a, - 0x2c, 0xdd, 0x5e, 0xa5, 0xb6, 0x7e, 0x14, 0x0f, 0x70, 0xa9, 0xb4, 0x5f, 0x64, 0x1b, 0xed, 0xf5, - 0x61, 0x32, 0x66, 0xd5, 0x61, 0x9d, 0x13, 0x88, 0xe5, 0x5b, 0xa1, 0x08, 0x3b, 0x9a, 0x48, 0x09, - 0x9e, 0xa0, 0x60, 0x8a, 0x93, 0x29, 0x43, 0xfd, 0xa1, 0xc8, 0x95, 0x8a, 0x50, 0x86, 0x92, 0xc1, - 0x24, 0x49, 0x3e, 0xae, 0xa4, 0x0f, 0xb8, 0x42, 0x1c, 0x1e, 0x9a, 0x92, 0x41, 0x70, 0x42, 0x49, - 0x4f, 0xd1, 0x70, 0x88, 0x9b, 0x21, 0xec, 0xe9, 0x96, 0x3d, 0xf0, 0x68, 0x84, 0x50, 0x89, 0x10, - 0xd6, 0x52, 0x34, 0x1c, 0xe2, 0xd6, 0xb6, 0x01, 0xb6, 0x06, 0xb6, 0xaf, 0xf3, 0x0c, 0x3c, 0x13, - 0xbb, 0x17, 0xe2, 0xf7, 0x8b, 0x30, 0x25, 0x60, 0xe5, 0x46, 0x7a, 0x09, 0x40, 0x26, 0xfa, 0x31, - 0x4d, 0x4f, 0xea, 0x06, 0xd1, 0x04, 0x17, 0x52, 0x30, 0xc6, 0x75, 0xba, 0x90, 0xb2, 0xd7, 0x61, - 0x4a, 0x85, 0x88, 0x71, 0xb5, 0x23, 0x15, 0x5e, 0xbb, 0x12, 0xa3, 0x61, 0x82, 0x93, 0xac, 0xb2, - 0xde, 0xdf, 0x15, 0x07, 0xcb, 0x2d, 0xd7, 0xe1, 0xb5, 0x45, 0x06, 0x86, 0xf0, 0x68, 0x65, 0x27, - 0x45, 0xc7, 0xa1, 0x1a, 0xe4, 0x8b, 0x50, 0xef, 0xe9, 0x0f, 0x77, 0x1c, 0xdd, 0x38, 0x90, 0x53, - 0x48, 0xa8, 0x57, 0x6c, 0xca, 0x72, 0x0c, 0x39, 0x88, 0x2e, 0xf7, 0xe1, 0xd5, 0xbc, 0x87, 0x0f, - 0xc3, 0x57, 0x36, 0xb4, 0x13, 0xff, 0x6f, 0x05, 0x20, 0xc3, 0xe7, 0x7a, 0xc8, 0x3e, 0x54, 0x1d, - 0x6e, 0x5c, 0xce, 0x7d, 0xb5, 0x44, 0xcc, 0x46, 0x2d, 0x56, 0x7d, 0x59, 0x20, 0xf1, 0x89, 0x03, - 0x75, 0xfa, 0x30, 0xa0, 0x9e, 0x13, 0x9e, 0xf3, 0x9b, 0xcc, 0x35, 0x16, 0x62, 0xb3, 0x2d, 0x91, - 0x31, 0x94, 0xa1, 0xfd, 0x6e, 0x11, 0x9a, 0x31, 0xbe, 0x27, 0xd9, 0x6c, 0x78, 0xaa, 0x11, 0x61, - 0xd3, 0xdd, 0xf1, 0x6c, 0x39, 0xb6, 0x62, 0xa9, 0x46, 0x24, 0x09, 0x37, 0x30, 0xce, 0xc7, 0x06, - 0x70, 0x4f, 0xf7, 0x83, 0xc4, 0x28, 0x0b, 0x07, 0xf0, 0x66, 0x48, 0xc1, 0x18, 0x17, 0xb9, 0x2e, - 0x2f, 0x22, 0x29, 0x27, 0x13, 0xb2, 0x8e, 0xb8, 0x65, 0xa4, 0x32, 0x81, 0x5b, 0x46, 0x48, 0x17, - 0x66, 0x55, 0xab, 0x15, 0xf5, 0x6c, 0xe9, 0x3a, 0xc5, 0xcc, 0x93, 0x82, 0xc0, 0x21, 0x50, 0xed, - 0xfb, 0x05, 0x98, 0x4e, 0x58, 0x14, 0x45, 0x2a, 0x55, 0x75, 0x2a, 0x2d, 0x91, 0x4a, 0x35, 0x76, - 0x98, 0xec, 0x65, 0xa8, 0x8a, 0x0e, 0x4a, 0x07, 0x9b, 0x8b, 0x2e, 0x44, 0x49, 0x65, 0xaa, 0x82, - 0xf4, 0x59, 0xa4, 0x55, 0x05, 0xe9, 0xd4, 0x40, 0x45, 0x17, 0xae, 0x40, 0xd1, 0x3a, 0xd9, 0xd3, - 0x31, 0x57, 0xa0, 0x28, 0xc7, 0x90, 0x43, 0xfb, 0xbb, 0xbc, 0xdd, 0x81, 0x77, 0x14, 0x9a, 0x4a, - 0xba, 0x50, 0x93, 0x01, 0xc6, 0xf2, 0xd3, 0x78, 0x33, 0x87, 0x99, 0x93, 0xe3, 0xc8, 0x10, 0x59, - 0xdd, 0x38, 0xb8, 0xb7, 0xb7, 0x87, 0x0a, 0x9d, 0xdc, 0x84, 0x86, 0xeb, 0xc8, 0x29, 0x59, 0x3e, - 0xfe, 0x17, 0x98, 0x2a, 0x70, 0x4f, 0x15, 0x3e, 0x3a, 0x6e, 0x5d, 0x0e, 0xff, 0x24, 0x1a, 0x89, - 0x51, 0x4d, 0xed, 0x4f, 0x15, 0xe0, 0x05, 0x74, 0x6d, 0xdb, 0x72, 0xba, 0x49, 0x57, 0x36, 0xb1, - 0x61, 0x46, 0xcc, 0x34, 0x87, 0xba, 0x65, 0xeb, 0xbb, 0x36, 0x7d, 0xa2, 0xa9, 0x63, 0x10, 0x58, - 0xf6, 0x82, 0xb8, 0x98, 0x75, 0x61, 0xdd, 0x09, 0xee, 0x79, 0x9d, 0xc0, 0xb3, 0x9c, 0xae, 0x58, - 0xf6, 0x36, 0x13, 0x58, 0x98, 0xc2, 0xd6, 0x7e, 0xaf, 0x04, 0x3c, 0x78, 0x95, 0x7c, 0x19, 0x1a, - 0x3d, 0x6a, 0xec, 0xeb, 0x8e, 0xe5, 0xab, 0xa4, 0xd4, 0x57, 0xd8, 0x73, 0x6d, 0xaa, 0xc2, 0x47, - 0xec, 0x55, 0x2c, 0x77, 0x36, 0xf8, 0x39, 0xb2, 0x88, 0x97, 0x18, 0x50, 0xed, 0xfa, 0xbe, 0xde, - 0xb7, 0x72, 0xc7, 0x0c, 0x89, 0x24, 0xc0, 0x62, 0x3a, 0x12, 0xbf, 0x51, 0x42, 0x13, 0x03, 0x2a, - 0x7d, 0x5b, 0xb7, 0x9c, 0xdc, 0x17, 0x09, 0xb2, 0x27, 0xd8, 0x62, 0x48, 0x62, 0xbd, 0xe3, 0x3f, - 0x51, 0x60, 0x93, 0x01, 0x34, 0x7d, 0xc3, 0xd3, 0x7b, 0xfe, 0xbe, 0xbe, 0xf4, 0xea, 0x6b, 0xb9, - 0x77, 0x73, 0x91, 0x28, 0xa1, 0x5c, 0xae, 0xe0, 0xf2, 0x66, 0xe7, 0xf6, 0xf2, 0xd2, 0xab, 0xaf, - 0x61, 0x5c, 0x4e, 0x5c, 0xec, 0xab, 0xaf, 0x2c, 0xc9, 0x19, 0x64, 0xe2, 0x62, 0x5f, 0x7d, 0x65, - 0x09, 0xe3, 0x72, 0xb4, 0xff, 0x55, 0x80, 0x46, 0xc8, 0x4b, 0x76, 0x00, 0xd8, 0x5c, 0x26, 0xd3, - 0xf6, 0x9e, 0x49, 0x71, 0xe0, 0xe6, 0xbb, 0x9d, 0xb0, 0x32, 0xc6, 0x80, 0x32, 0xf2, 0x1a, 0x17, - 0x27, 0x9d, 0xd7, 0x78, 0x11, 0x1a, 0xfb, 0xba, 0x63, 0xfa, 0xfb, 0xfa, 0x01, 0x95, 0x31, 0xff, - 0xe1, 0xde, 0xf2, 0xb6, 0x22, 0x60, 0xc4, 0xa3, 0xfd, 0xfd, 0x2a, 0x88, 0x40, 0x1f, 0x36, 0xe9, - 0x98, 0x96, 0x2f, 0x4e, 0xe6, 0x14, 0x78, 0xcd, 0x70, 0xd2, 0x59, 0x95, 0xe5, 0x18, 0x72, 0x90, - 0x2b, 0x50, 0xea, 0x59, 0x8e, 0x54, 0x29, 0xb9, 0x65, 0x7e, 0xd3, 0x72, 0x90, 0x95, 0x71, 0x92, - 0xfe, 0x50, 0xaa, 0x8c, 0x82, 0xa4, 0x3f, 0x44, 0x56, 0x46, 0xbe, 0x0a, 0x17, 0x6c, 0xd7, 0x3d, - 0x60, 0xd3, 0x47, 0x3c, 0x76, 0x79, 0x5a, 0xd8, 0xca, 0x36, 0x92, 0x24, 0x4c, 0xf3, 0x92, 0x1d, - 0x78, 0xf1, 0x03, 0xea, 0xb9, 0x72, 0xbe, 0xec, 0xd8, 0x94, 0xf6, 0x15, 0x8c, 0x50, 0x54, 0x78, - 0x68, 0xf5, 0xcf, 0x65, 0xb3, 0xe0, 0xa8, 0xba, 0xfc, 0x30, 0x88, 0xee, 0x75, 0x69, 0xb0, 0xe5, - 0xb9, 0x4c, 0x19, 0xb5, 0x9c, 0xae, 0x82, 0xad, 0x46, 0xb0, 0xdb, 0xd9, 0x2c, 0x38, 0xaa, 0x2e, - 0x79, 0x07, 0xe6, 0x04, 0x49, 0xa8, 0x2d, 0xcb, 0x62, 0x9a, 0xb1, 0x6c, 0x75, 0xff, 0xee, 0xb4, - 0x70, 0x80, 0x6e, 0x8f, 0xe0, 0xc1, 0x91, 0xb5, 0xc9, 0x5b, 0x30, 0xab, 0xdc, 0xdf, 0x5b, 0xd4, - 0xeb, 0x84, 0xc1, 0x5f, 0xd3, 0x2a, 0x06, 0x5e, 0xc5, 0x80, 0x63, 0x8a, 0x0b, 0x87, 0xea, 0x11, - 0x84, 0xcb, 0x3c, 0xc2, 0x6b, 0xa7, 0xbf, 0xe2, 0xba, 0xb6, 0xe9, 0x3e, 0x70, 0xd4, 0xb3, 0x8b, - 0x1d, 0x18, 0xf7, 0x78, 0x77, 0x32, 0x39, 0x70, 0x44, 0x4d, 0xf6, 0xe4, 0x9c, 0xb2, 0xea, 0x3e, - 0x70, 0xd2, 0xa8, 0x10, 0x3d, 0x79, 0x67, 0x04, 0x0f, 0x8e, 0xac, 0x4d, 0xd6, 0x80, 0xa4, 0x9f, - 0x60, 0xa7, 0x2f, 0x63, 0x32, 0x2e, 0x8b, 0x0c, 0x5c, 0x69, 0x2a, 0x66, 0xd4, 0x20, 0x1b, 0x70, - 0x29, 0x5d, 0xca, 0xc4, 0xc9, 0xf0, 0x0c, 0x9e, 0x7b, 0x1b, 0x33, 0xe8, 0x98, 0x59, 0x4b, 0xfb, - 0x07, 0x45, 0x98, 0x4e, 0xa4, 0x6c, 0x79, 0xe6, 0x52, 0x63, 0xb0, 0xdd, 0x60, 0xcf, 0xef, 0xae, - 0xaf, 0xde, 0xa6, 0xba, 0x49, 0x3d, 0x75, 0x22, 0xa8, 0x21, 0x97, 0xc5, 0x04, 0x05, 0x53, 0x9c, - 0x64, 0x0f, 0x2a, 0xc2, 0xf1, 0x93, 0xf7, 0xfa, 0x2e, 0xd5, 0x47, 0xdc, 0xfb, 0x23, 0xef, 0xbc, - 0x73, 0x3d, 0x8a, 0x02, 0x5e, 0x0b, 0x60, 0x2a, 0xce, 0xc1, 0x26, 0x92, 0x48, 0xed, 0xad, 0x25, - 0x54, 0xde, 0x75, 0x28, 0x05, 0xc1, 0xb8, 0x49, 0x37, 0x84, 0x23, 0x71, 0x7b, 0x03, 0x19, 0x86, - 0xb6, 0xc7, 0xde, 0x9d, 0xef, 0x5b, 0xae, 0x23, 0x6f, 0x60, 0xd8, 0x81, 0x9a, 0xdc, 0x0e, 0x8f, - 0x99, 0x34, 0x84, 0xeb, 0x4a, 0xca, 0x8e, 0xae, 0xb0, 0xb4, 0x7f, 0x53, 0x84, 0x46, 0x68, 0xf7, - 0x3a, 0xc5, 0xcd, 0x06, 0x2e, 0x34, 0xc2, 0x08, 0xd5, 0xdc, 0x77, 0x13, 0x47, 0x81, 0x93, 0xdc, - 0x54, 0x13, 0xfe, 0xc5, 0x48, 0x46, 0x3c, 0xfa, 0xb5, 0x94, 0x23, 0xfa, 0xb5, 0x0f, 0xb5, 0xc0, - 0xb3, 0xba, 0x5d, 0xb9, 0x4b, 0xc8, 0x13, 0xfe, 0x1a, 0x76, 0xd7, 0xb6, 0x00, 0x94, 0x3d, 0x2b, - 0xfe, 0xa0, 0x12, 0xa3, 0xbd, 0x0f, 0xb3, 0x69, 0x4e, 0xae, 0x42, 0x1b, 0xfb, 0xd4, 0x1c, 0xd8, - 0xaa, 0x8f, 0x23, 0x15, 0x5a, 0x96, 0x63, 0xc8, 0x41, 0x6e, 0x40, 0x9d, 0xbd, 0xa6, 0x0f, 0x5c, - 0x47, 0xa9, 0xb1, 0x7c, 0x37, 0xb2, 0x2d, 0xcb, 0x30, 0xa4, 0x6a, 0xff, 0xb9, 0x04, 0x57, 0x22, - 0xeb, 0xe5, 0xa6, 0xee, 0xe8, 0xdd, 0x53, 0x5c, 0x48, 0xfb, 0xe9, 0x31, 0xcc, 0xb3, 0x5e, 0x4f, - 0x53, 0x7a, 0x06, 0xae, 0xa7, 0xf9, 0xbf, 0x45, 0xe0, 0xd1, 0xf4, 0xe4, 0xdb, 0x30, 0xa5, 0xc7, - 0xee, 0x22, 0x97, 0xaf, 0xf3, 0x66, 0xee, 0xd7, 0xc9, 0x83, 0xf6, 0x43, 0xc3, 0x4e, 0xbc, 0x14, - 0x13, 0x02, 0x89, 0x0b, 0xf5, 0x3d, 0xdd, 0xb6, 0x99, 0x2e, 0x94, 0xdb, 0x1b, 0x9b, 0x10, 0xce, - 0x87, 0xf9, 0x9a, 0x84, 0xc6, 0x50, 0x08, 0xf9, 0x6e, 0x01, 0xa6, 0xbd, 0xf8, 0x76, 0x4d, 0xbe, - 0x90, 0x3c, 0xb1, 0x3a, 0x31, 0xb4, 0x78, 0xfc, 0x64, 0x7c, 0x4f, 0x98, 0x94, 0xa9, 0xfd, 0xc7, - 0x02, 0x4c, 0x77, 0x6c, 0xcb, 0xb4, 0x9c, 0xee, 0x39, 0xde, 0x8e, 0x73, 0x0f, 0x2a, 0xbe, 0x6d, - 0x99, 0x74, 0xcc, 0xd5, 0x44, 0xac, 0x63, 0x0c, 0x00, 0x05, 0x4e, 0xf2, 0xba, 0x9d, 0xd2, 0x29, - 0xae, 0xdb, 0xf9, 0x4f, 0x35, 0x90, 0xe7, 0x42, 0xc8, 0x00, 0x1a, 0x5d, 0x75, 0x8b, 0x87, 0x7c, - 0xc6, 0xdb, 0x39, 0x32, 0xc0, 0x26, 0xee, 0x03, 0x11, 0x73, 0x7f, 0x58, 0x88, 0x91, 0x24, 0x42, - 0x93, 0x97, 0xe0, 0xaf, 0xe6, 0xbc, 0x04, 0x5f, 0x88, 0x1b, 0xbe, 0x06, 0x5f, 0x87, 0xf2, 0x7e, - 0x10, 0xf4, 0xe5, 0x60, 0x1a, 0xdf, 0x58, 0x18, 0x25, 0x21, 0x13, 0x3a, 0x11, 0xfb, 0x8f, 0x1c, - 0x9a, 0x89, 0x70, 0xf4, 0xf0, 0xaa, 0xd1, 0x95, 0x5c, 0x71, 0x41, 0x71, 0x11, 0xec, 0x3f, 0x72, - 0x68, 0xf2, 0x0b, 0xd0, 0x0c, 0x3c, 0xdd, 0xf1, 0xf7, 0x5c, 0xaf, 0x47, 0x3d, 0xb9, 0x47, 0x5d, - 0xcb, 0x71, 0x0f, 0xfc, 0x76, 0x84, 0x26, 0x6c, 0xec, 0x89, 0x22, 0x8c, 0x4b, 0x23, 0x07, 0x50, - 0x1f, 0x98, 0xa2, 0x61, 0xd2, 0x0c, 0xb6, 0x9c, 0xe7, 0x6a, 0xff, 0x58, 0xd4, 0x8f, 0xfa, 0x87, - 0xa1, 0x80, 0xe4, 0xad, 0xba, 0xb5, 0x49, 0xdd, 0xaa, 0x1b, 0x1f, 0x8d, 0x59, 0x19, 0x92, 0x48, - 0x4f, 0xea, 0xb5, 0x4e, 0x57, 0x06, 0x2d, 0xae, 0xe5, 0x56, 0x39, 0x85, 0xc8, 0x66, 0xa8, 0x1b, - 0x3b, 0x5d, 0x54, 0x32, 0x88, 0x05, 0xd5, 0x3e, 0xb7, 0x3e, 0xe7, 0xbe, 0x61, 0x3d, 0xee, 0x20, - 0x10, 0x73, 0x8d, 0x28, 0x41, 0x29, 0x40, 0xeb, 0x81, 0xf4, 0x3b, 0x12, 0x23, 0x71, 0x69, 0x99, - 0x38, 0x55, 0xbb, 0x78, 0xba, 0xa9, 0x27, 0xbc, 0x3d, 0x2b, 0x76, 0x69, 0x42, 0xe6, 0xed, 0x64, - 0xda, 0xbf, 0x2d, 0x42, 0x69, 0x7b, 0xa3, 0x23, 0x12, 0x21, 0xf3, 0x6b, 0x10, 0x69, 0xe7, 0xc0, - 0xea, 0xdf, 0xa7, 0x9e, 0xb5, 0x77, 0x24, 0x77, 0xf9, 0xb1, 0x44, 0xc8, 0x69, 0x0e, 0xcc, 0xa8, - 0x45, 0xde, 0x85, 0x29, 0x43, 0x5f, 0xa1, 0x5e, 0x30, 0x8e, 0x0d, 0x83, 0xa7, 0x42, 0x58, 0x59, - 0x8e, 0xaa, 0x63, 0x02, 0x8c, 0xec, 0x00, 0x18, 0x11, 0x74, 0xe9, 0xcc, 0x96, 0x97, 0x18, 0x70, - 0x0c, 0x88, 0x20, 0x34, 0x0e, 0x18, 0x2b, 0x47, 0x2d, 0x9f, 0x05, 0x95, 0x0f, 0xd2, 0x3b, 0xaa, - 0x2e, 0x46, 0x30, 0x9a, 0x03, 0xd3, 0x89, 0x9b, 0xcc, 0xc8, 0x57, 0xa0, 0xee, 0xf6, 0x63, 0x33, - 0x77, 0x83, 0x47, 0x62, 0xd7, 0xef, 0xc9, 0xb2, 0x47, 0xc7, 0xad, 0xe9, 0x0d, 0xb7, 0x6b, 0x19, - 0xaa, 0x00, 0x43, 0x76, 0xa2, 0x41, 0x95, 0x9f, 0xf9, 0x55, 0xf7, 0x98, 0xf1, 0xa1, 0xc3, 0xaf, - 0x1a, 0xf2, 0x51, 0x52, 0xb4, 0x5f, 0x2a, 0x43, 0xe4, 0xad, 0x27, 0x3e, 0x54, 0xc5, 0x79, 0x23, - 0xb9, 0x48, 0x9c, 0xeb, 0xd1, 0x26, 0x29, 0x8a, 0x74, 0xa1, 0xf4, 0xbe, 0xbb, 0x9b, 0x7b, 0x8d, - 0x88, 0x25, 0x2e, 0x11, 0x66, 0xb9, 0x58, 0x01, 0x32, 0x09, 0xe4, 0x2f, 0x17, 0xe0, 0x79, 0x3f, - 0xad, 0x65, 0xcb, 0xe1, 0x80, 0xf9, 0xb7, 0x13, 0x69, 0xbd, 0x5d, 0x86, 0xcc, 0x8f, 0x22, 0xe3, - 0x70, 0x5b, 0x58, 0xff, 0x0b, 0x37, 0xba, 0x1c, 0x4e, 0xb7, 0x72, 0xde, 0xd7, 0x9c, 0xec, 0xff, - 0x64, 0x19, 0x4a, 0x51, 0xda, 0x77, 0x8a, 0xd0, 0x8c, 0x2d, 0x0c, 0xb9, 0xaf, 0xc7, 0x7b, 0x98, - 0xba, 0x1e, 0x6f, 0x6b, 0xfc, 0xa8, 0x92, 0xa8, 0x55, 0xe7, 0x7d, 0x43, 0xde, 0x3f, 0x2e, 0x42, - 0x69, 0x67, 0x75, 0x2d, 0xb9, 0x3f, 0x2e, 0x3c, 0x85, 0xfd, 0xf1, 0x3e, 0xd4, 0x76, 0x07, 0x96, - 0x1d, 0x58, 0x4e, 0xee, 0xd4, 0x4a, 0xea, 0x36, 0x41, 0xe9, 0x56, 0x11, 0xa8, 0xa8, 0xe0, 0x49, - 0x17, 0x6a, 0x5d, 0x91, 0xdb, 0x36, 0x77, 0xac, 0xad, 0xcc, 0x91, 0x2b, 0x04, 0xc9, 0x3f, 0xa8, - 0xd0, 0xb5, 0x23, 0xa8, 0xee, 0xac, 0xca, 0x1d, 0xc6, 0xd3, 0xed, 0x4d, 0xed, 0x17, 0x20, 0x54, - 0x38, 0x9e, 0xbe, 0xf0, 0xff, 0x5a, 0x80, 0xa4, 0x8e, 0xf5, 0xf4, 0x47, 0xd3, 0x41, 0x7a, 0x34, - 0xad, 0x4e, 0xe2, 0xe3, 0xcb, 0x1e, 0x50, 0xda, 0xbf, 0x2a, 0x40, 0xea, 0x90, 0x28, 0x79, 0x4d, - 0xa6, 0x49, 0x4c, 0x06, 0x35, 0xaa, 0x34, 0x89, 0x24, 0xc9, 0x1d, 0x4b, 0x97, 0xf8, 0x21, 0xdb, - 0x19, 0xc6, 0x7d, 0x75, 0xb2, 0xf9, 0x77, 0xc7, 0xdf, 0x19, 0x66, 0x79, 0xfe, 0x64, 0xe0, 0x6d, - 0x9c, 0x84, 0x49, 0xb9, 0xda, 0xdf, 0x2b, 0x42, 0xf5, 0xa9, 0xe5, 0xc5, 0xa0, 0x89, 0x58, 0xe8, - 0x95, 0x9c, 0xb3, 0xfd, 0xc8, 0x48, 0xe8, 0x5e, 0x2a, 0x12, 0xfa, 0x66, 0x5e, 0x41, 0x8f, 0x8f, - 0x83, 0xfe, 0x17, 0x05, 0x90, 0x6b, 0xcd, 0xba, 0xe3, 0x07, 0xba, 0x63, 0x50, 0x62, 0x84, 0x0b, - 0x5b, 0xde, 0x80, 0x3b, 0x19, 0x94, 0x2a, 0x74, 0x19, 0xfe, 0x5b, 0x2d, 0x64, 0xe4, 0x8b, 0x50, - 0xdf, 0x77, 0xfd, 0x80, 0x2f, 0x5e, 0xc5, 0xa4, 0x75, 0xee, 0xb6, 0x2c, 0xc7, 0x90, 0x23, 0xed, - 0x39, 0xaf, 0x8c, 0xf6, 0x9c, 0x6b, 0xbf, 0x51, 0x84, 0xa9, 0x4f, 0x4a, 0xe2, 0x8d, 0xac, 0xc8, - 0xf1, 0x52, 0xce, 0xc8, 0xf1, 0xf2, 0x59, 0x22, 0xc7, 0xb5, 0x1f, 0x16, 0x00, 0x9e, 0x5a, 0xd6, - 0x0f, 0x33, 0x19, 0xd4, 0x9d, 0x7b, 0x5c, 0x65, 0x87, 0x74, 0xff, 0xed, 0x8a, 0x7a, 0x24, 0x1e, - 0xd0, 0xfd, 0x61, 0x01, 0x66, 0xf4, 0x44, 0x90, 0x74, 0x6e, 0x7d, 0x39, 0x15, 0x73, 0x1d, 0xc6, - 0xf8, 0x25, 0xcb, 0x31, 0x25, 0x96, 0xbc, 0x1e, 0x65, 0xe8, 0xbf, 0x1b, 0x0d, 0xfb, 0xa1, 0xd4, - 0xfa, 0x22, 0xaa, 0x2b, 0xce, 0xf9, 0x84, 0xa0, 0xf4, 0xd2, 0x44, 0x82, 0xd2, 0xe3, 0xc7, 0x6d, - 0xcb, 0x8f, 0x3d, 0x6e, 0x7b, 0x08, 0x8d, 0x3d, 0xcf, 0xed, 0xf1, 0xb8, 0x6f, 0x79, 0x07, 0xfe, - 0xcd, 0x1c, 0x0b, 0x65, 0x6f, 0xd7, 0x72, 0xa8, 0xc9, 0x63, 0xca, 0x43, 0x1b, 0xd9, 0x9a, 0xc2, - 0xc7, 0x48, 0x14, 0x77, 0x2b, 0xb8, 0x42, 0x6a, 0x75, 0x92, 0x52, 0xc3, 0xb9, 0x64, 0x5b, 0xa0, - 0xa3, 0x12, 0x93, 0x8c, 0xf5, 0xae, 0x3d, 0x9d, 0x58, 0x6f, 0xed, 0xcf, 0xd6, 0xd4, 0x04, 0xf6, - 0xcc, 0x25, 0x83, 0xfe, 0x34, 0x49, 0x42, 0x97, 0x0e, 0x65, 0x30, 0xa8, 0x3f, 0xc5, 0x0c, 0x06, - 0x8d, 0xc9, 0x64, 0x30, 0x80, 0x7c, 0x19, 0x0c, 0x9a, 0x13, 0xca, 0x60, 0x30, 0x35, 0xa9, 0x0c, - 0x06, 0xd3, 0x63, 0x65, 0x30, 0x98, 0x39, 0x55, 0x06, 0x83, 0xe3, 0x12, 0xa4, 0x36, 0xe3, 0x9f, - 0xfa, 0xf8, 0xfe, 0x50, 0xf9, 0xf8, 0xbe, 0x57, 0x84, 0x68, 0x22, 0x3e, 0x63, 0x0c, 0xd4, 0x3b, - 0x3c, 0x50, 0x9a, 0x07, 0xdd, 0xe7, 0xb9, 0x43, 0x7d, 0x53, 0x62, 0x60, 0x88, 0x46, 0x7c, 0x00, - 0x2b, 0xbc, 0xc7, 0x24, 0xb7, 0xb7, 0x24, 0xba, 0x12, 0x45, 0x18, 0x49, 0xa3, 0xff, 0x18, 0x13, - 0xa3, 0xfd, 0xf3, 0x22, 0xc8, 0x0b, 0x6f, 0x08, 0x85, 0xca, 0x9e, 0xf5, 0x90, 0x9a, 0xb9, 0x23, - 0xab, 0xd7, 0x18, 0x8a, 0xbc, 0x55, 0x87, 0xbb, 0x83, 0x78, 0x01, 0x0a, 0x74, 0x6e, 0xe7, 0x17, - 0xee, 0x3d, 0xd9, 0x7f, 0x39, 0xec, 0xfc, 0x71, 0x37, 0xa1, 0xb4, 0xf3, 0x8b, 0x22, 0x54, 0x32, - 0x84, 0x5b, 0x81, 0x47, 0x7a, 0xe4, 0xf6, 0x66, 0x26, 0x22, 0x46, 0x94, 0x5b, 0xc1, 0x17, 0x29, - 0x4c, 0xa4, 0x8c, 0xf6, 0xcf, 0xff, 0xe0, 0x47, 0xd7, 0x9e, 0xfb, 0xe1, 0x8f, 0xae, 0x3d, 0xf7, - 0xd1, 0x8f, 0xae, 0x3d, 0xf7, 0x4b, 0x27, 0xd7, 0x0a, 0x3f, 0x38, 0xb9, 0x56, 0xf8, 0xe1, 0xc9, - 0xb5, 0xc2, 0x47, 0x27, 0xd7, 0x0a, 0xff, 0xfe, 0xe4, 0x5a, 0xe1, 0x2f, 0xfc, 0x87, 0x6b, 0xcf, - 0xfd, 0xdc, 0x97, 0xa3, 0x26, 0x2c, 0xaa, 0x26, 0x2c, 0x2a, 0x81, 0x8b, 0xfd, 0x83, 0xee, 0x22, - 0x6b, 0x42, 0x54, 0xa2, 0x9a, 0xf0, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0x94, 0x34, 0x30, 0x66, - 0x45, 0xa2, 0x00, 0x00, + // 8368 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x6f, 0x6c, 0x24, 0x49, + 0x96, 0xd7, 0xd4, 0xff, 0xaa, 0x57, 0xb6, 0xdb, 0x13, 0xdd, 0xd3, 0xe3, 0xf6, 0xf6, 0x74, 0xf5, + 0xe6, 0xde, 0xce, 0xf6, 0x71, 0x7b, 0x36, 0xe3, 0xdb, 0x99, 0x9d, 0xbd, 0xbd, 0xdd, 0x19, 0x97, + 0xdd, 0xee, 0xf6, 0xb4, 0xdd, 0xed, 0x7d, 0x65, 0xf7, 0xcc, 0xde, 0x70, 0x3b, 0xa4, 0x33, 0xc3, + 0xe5, 0x1c, 0x67, 0x65, 0xd6, 0x66, 0x66, 0xb9, 0xdb, 0x73, 0x9c, 0xf6, 0x6e, 0x17, 0x34, 0x8b, + 0x00, 0x81, 0xee, 0xd3, 0x49, 0xe8, 0x40, 0x20, 0xa4, 0xfb, 0x70, 0x3a, 0x3e, 0x9c, 0x58, 0x3e, + 0x20, 0xf1, 0xe7, 0x10, 0x82, 0xe5, 0xff, 0x0a, 0x21, 0xb1, 0x48, 0x60, 0xb1, 0x46, 0x08, 0x81, + 0x04, 0x3a, 0x38, 0x01, 0x47, 0x0b, 0x71, 0x28, 0xfe, 0xe5, 0xbf, 0xca, 0xea, 0xb6, 0x2b, 0xcb, + 0x3d, 0x3d, 0xcb, 0x7c, 0xab, 0x8a, 0xf7, 0xe2, 0xf7, 0x22, 0x23, 0x23, 0x23, 0x5e, 0xbc, 0xf7, + 0xe2, 0x05, 0xdc, 0xea, 0x5a, 0xc1, 0xfe, 0x60, 0x77, 0xc1, 0x70, 0x7b, 0x8b, 0xce, 0xa0, 0xa7, + 0xf7, 0x3d, 0xf7, 0x7d, 0xfe, 0x63, 0xcf, 0x76, 0x1f, 0x2c, 0xf6, 0x0f, 0xba, 0x8b, 0x7a, 0xdf, + 0xf2, 0xa3, 0x92, 0xc3, 0x57, 0x74, 0xbb, 0xbf, 0xaf, 0xbf, 0xb2, 0xd8, 0xa5, 0x0e, 0xf5, 0xf4, + 0x80, 0x9a, 0x0b, 0x7d, 0xcf, 0x0d, 0x5c, 0xf2, 0xc5, 0x08, 0x68, 0x41, 0x01, 0x2d, 0xa8, 0x6a, + 0x0b, 0xfd, 0x83, 0xee, 0x02, 0x03, 0x8a, 0x4a, 0x14, 0xd0, 0xfc, 0x4f, 0xc7, 0x5a, 0xd0, 0x75, + 0xbb, 0xee, 0x22, 0xc7, 0xdb, 0x1d, 0xec, 0xf1, 0x7f, 0xfc, 0x0f, 0xff, 0x25, 0xe4, 0xcc, 0x6b, + 0x07, 0xaf, 0xfb, 0x0b, 0x96, 0xcb, 0x9a, 0xb5, 0x68, 0xb8, 0x1e, 0x5d, 0x3c, 0x1c, 0x6a, 0xcb, + 0xfc, 0x17, 0x22, 0x9e, 0x9e, 0x6e, 0xec, 0x5b, 0x0e, 0xf5, 0x8e, 0xd4, 0xb3, 0x2c, 0x7a, 0xd4, + 0x77, 0x07, 0x9e, 0x41, 0xcf, 0x54, 0xcb, 0x5f, 0xec, 0xd1, 0x40, 0xcf, 0x92, 0xb5, 0x38, 0xaa, + 0x96, 0x37, 0x70, 0x02, 0xab, 0x37, 0x2c, 0xe6, 0xb5, 0x27, 0x55, 0xf0, 0x8d, 0x7d, 0xda, 0xd3, + 0x87, 0xea, 0xfd, 0xcc, 0xa8, 0x7a, 0x83, 0xc0, 0xb2, 0x17, 0x2d, 0x27, 0xf0, 0x03, 0x2f, 0x5d, + 0x49, 0xfb, 0x1d, 0x80, 0x8b, 0xcb, 0xbb, 0x7e, 0xe0, 0xe9, 0x46, 0xb0, 0xe5, 0x9a, 0xdb, 0xb4, + 0xd7, 0xb7, 0xf5, 0x80, 0x92, 0x03, 0xa8, 0xb3, 0x07, 0x32, 0xf5, 0x40, 0x9f, 0x2b, 0x5c, 0x2f, + 0xdc, 0x68, 0x2e, 0x2d, 0x2f, 0x8c, 0xf9, 0x02, 0x17, 0x36, 0x25, 0x50, 0x7b, 0xea, 0xe4, 0xb8, + 0x55, 0x57, 0xff, 0x30, 0x14, 0x40, 0x7e, 0xad, 0x00, 0x53, 0x8e, 0x6b, 0xd2, 0x0e, 0xb5, 0xa9, + 0x11, 0xb8, 0xde, 0x5c, 0xf1, 0x7a, 0xe9, 0x46, 0x73, 0xe9, 0x1b, 0x63, 0x4b, 0xcc, 0x78, 0xa2, + 0x85, 0xbb, 0x31, 0x01, 0x37, 0x9d, 0xc0, 0x3b, 0x6a, 0x5f, 0xfa, 0xfe, 0x71, 0xeb, 0xb9, 0x93, + 0xe3, 0xd6, 0x54, 0x9c, 0x84, 0x89, 0x96, 0x90, 0x1d, 0x68, 0x06, 0xae, 0xcd, 0xba, 0xcc, 0x72, + 0x1d, 0x7f, 0xae, 0xc4, 0x1b, 0x76, 0x6d, 0x41, 0x74, 0x35, 0x13, 0xbf, 0xc0, 0xc6, 0xd8, 0xc2, + 0xe1, 0x2b, 0x0b, 0xdb, 0x21, 0x5b, 0xfb, 0xa2, 0x04, 0x6e, 0x46, 0x65, 0x3e, 0xc6, 0x71, 0x08, + 0x85, 0x0b, 0x3e, 0x35, 0x06, 0x9e, 0x15, 0x1c, 0xad, 0xb8, 0x4e, 0x40, 0x1f, 0x06, 0x73, 0x65, + 0xde, 0xcb, 0x2f, 0x67, 0x41, 0x6f, 0xb9, 0x66, 0x27, 0xc9, 0xdd, 0xbe, 0x78, 0x72, 0xdc, 0xba, + 0x90, 0x2a, 0xc4, 0x34, 0x26, 0x71, 0x60, 0xd6, 0xea, 0xe9, 0x5d, 0xba, 0x35, 0xb0, 0xed, 0x0e, + 0x35, 0x3c, 0x1a, 0xf8, 0x73, 0x15, 0xfe, 0x08, 0x37, 0xb2, 0xe4, 0x6c, 0xb8, 0x86, 0x6e, 0xdf, + 0xdb, 0x7d, 0x9f, 0x1a, 0x01, 0xd2, 0x3d, 0xea, 0x51, 0xc7, 0xa0, 0xed, 0x39, 0xf9, 0x30, 0xb3, + 0xeb, 0x29, 0x24, 0x1c, 0xc2, 0x26, 0xb7, 0xe0, 0xf9, 0xbe, 0x67, 0xb9, 0xbc, 0x09, 0xb6, 0xee, + 0xfb, 0x77, 0xf5, 0x1e, 0x9d, 0xab, 0x5e, 0x2f, 0xdc, 0x68, 0xb4, 0xaf, 0x48, 0x98, 0xe7, 0xb7, + 0xd2, 0x0c, 0x38, 0x5c, 0x87, 0xdc, 0x80, 0xba, 0x2a, 0x9c, 0xab, 0x5d, 0x2f, 0xdc, 0xa8, 0x88, + 0xb1, 0xa3, 0xea, 0x62, 0x48, 0x25, 0x6b, 0x50, 0xd7, 0xf7, 0xf6, 0x2c, 0x87, 0x71, 0xd6, 0x79, + 0x17, 0x5e, 0xcd, 0x7a, 0xb4, 0x65, 0xc9, 0x23, 0x70, 0xd4, 0x3f, 0x0c, 0xeb, 0x92, 0xb7, 0x80, + 0xf8, 0xd4, 0x3b, 0xb4, 0x0c, 0xba, 0x6c, 0x18, 0xee, 0xc0, 0x09, 0x78, 0xdb, 0x1b, 0xbc, 0xed, + 0xf3, 0xb2, 0xed, 0xa4, 0x33, 0xc4, 0x81, 0x19, 0xb5, 0xc8, 0x9b, 0x30, 0x2b, 0xbf, 0xd5, 0xa8, + 0x17, 0x80, 0x23, 0x5d, 0x62, 0x1d, 0x89, 0x29, 0x1a, 0x0e, 0x71, 0x13, 0x13, 0xae, 0xea, 0x83, + 0xc0, 0xed, 0x31, 0xc8, 0xa4, 0xd0, 0x6d, 0xf7, 0x80, 0x3a, 0x73, 0xcd, 0xeb, 0x85, 0x1b, 0xf5, + 0xf6, 0xf5, 0x93, 0xe3, 0xd6, 0xd5, 0xe5, 0xc7, 0xf0, 0xe1, 0x63, 0x51, 0xc8, 0x3d, 0x68, 0x98, + 0x8e, 0xbf, 0xe5, 0xda, 0x96, 0x71, 0x34, 0x37, 0xc5, 0x1b, 0xf8, 0x8a, 0x7c, 0xd4, 0xc6, 0xea, + 0xdd, 0x8e, 0x20, 0x3c, 0x3a, 0x6e, 0x5d, 0x1d, 0x9e, 0x52, 0x17, 0x42, 0x3a, 0x46, 0x18, 0x64, + 0x93, 0x03, 0xae, 0xb8, 0xce, 0x9e, 0xd5, 0x9d, 0x9b, 0xe6, 0x6f, 0xe3, 0xfa, 0x88, 0x01, 0xbd, + 0x7a, 0xb7, 0x23, 0xf8, 0xda, 0xd3, 0x52, 0x9c, 0xf8, 0x8b, 0x11, 0x02, 0x31, 0x61, 0x46, 0x4d, + 0xc6, 0x2b, 0xb6, 0x6e, 0xf5, 0xfc, 0xb9, 0x19, 0x3e, 0x78, 0x7f, 0x62, 0x04, 0x26, 0xc6, 0x99, + 0xdb, 0x97, 0xe5, 0xa3, 0xcc, 0x24, 0x8a, 0x7d, 0x4c, 0x61, 0xce, 0xbf, 0x01, 0xcf, 0x0f, 0xcd, + 0x0d, 0x64, 0x16, 0x4a, 0x07, 0xf4, 0x88, 0x4f, 0x7d, 0x0d, 0x64, 0x3f, 0xc9, 0x25, 0xa8, 0x1c, + 0xea, 0xf6, 0x80, 0xce, 0x15, 0x79, 0x99, 0xf8, 0xf3, 0xb3, 0xc5, 0xd7, 0x0b, 0xda, 0x5f, 0x2e, + 0xc1, 0x94, 0x9a, 0x71, 0x3a, 0x96, 0x73, 0x40, 0xde, 0x86, 0x92, 0xed, 0x76, 0xe5, 0xbc, 0xf9, + 0x73, 0x63, 0xcf, 0x62, 0x1b, 0x6e, 0xb7, 0x5d, 0x3b, 0x39, 0x6e, 0x95, 0x36, 0xdc, 0x2e, 0x32, + 0x44, 0x62, 0x40, 0xe5, 0x40, 0xdf, 0x3b, 0xd0, 0x79, 0x1b, 0x9a, 0x4b, 0xed, 0xb1, 0xa1, 0xef, + 0x30, 0x14, 0xd6, 0xd6, 0x76, 0xe3, 0xe4, 0xb8, 0x55, 0xe1, 0x7f, 0x51, 0x60, 0x13, 0x17, 0x1a, + 0xbb, 0xb6, 0x6e, 0x1c, 0xec, 0xbb, 0x36, 0x9d, 0x2b, 0xe5, 0x14, 0xd4, 0x56, 0x48, 0xe2, 0x35, + 0x87, 0x7f, 0x31, 0x92, 0x41, 0x0c, 0xa8, 0x0e, 0x4c, 0xdf, 0x72, 0x0e, 0xe4, 0x1c, 0xf8, 0xc6, + 0xd8, 0xd2, 0x76, 0x56, 0xf9, 0x33, 0xc1, 0xc9, 0x71, 0xab, 0x2a, 0x7e, 0xa3, 0x84, 0xd6, 0x7e, + 0x7f, 0x0a, 0x66, 0xd4, 0x4b, 0xba, 0x4f, 0xbd, 0x80, 0x3e, 0x24, 0xd7, 0xa1, 0xec, 0xb0, 0x4f, + 0x93, 0xbf, 0xe4, 0xf6, 0x94, 0x1c, 0x2e, 0x65, 0xfe, 0x49, 0x72, 0x0a, 0x6b, 0x99, 0x18, 0x2a, + 0xb2, 0xc3, 0xc7, 0x6f, 0x59, 0x87, 0xc3, 0x88, 0x96, 0x89, 0xdf, 0x28, 0xa1, 0xc9, 0xbb, 0x50, + 0xe6, 0x0f, 0x2f, 0xba, 0xfa, 0x2b, 0xe3, 0x8b, 0x60, 0x8f, 0x5e, 0x67, 0x4f, 0xc0, 0x1f, 0x9c, + 0x83, 0xb2, 0xa1, 0x38, 0x30, 0xf7, 0x64, 0xc7, 0xfe, 0x5c, 0x8e, 0x8e, 0x5d, 0x13, 0x43, 0x71, + 0x67, 0x75, 0x0d, 0x19, 0x22, 0xf9, 0xb3, 0x05, 0x78, 0xde, 0x70, 0x9d, 0x40, 0x67, 0x7a, 0x86, + 0x5a, 0x64, 0xe7, 0x2a, 0x5c, 0xce, 0x5b, 0x63, 0xcb, 0x59, 0x49, 0x23, 0xb6, 0x5f, 0x60, 0x6b, + 0xc6, 0x50, 0x31, 0x0e, 0xcb, 0x26, 0x7f, 0xbe, 0x00, 0x2f, 0xb0, 0xb9, 0x7c, 0x88, 0x99, 0xaf, + 0x40, 0x93, 0x6d, 0xd5, 0x95, 0x93, 0xe3, 0xd6, 0x0b, 0xeb, 0x59, 0xc2, 0x30, 0xbb, 0x0d, 0xac, + 0x75, 0x17, 0xf5, 0x61, 0xb5, 0x84, 0xaf, 0x6e, 0xcd, 0xa5, 0x8d, 0x49, 0xaa, 0x3a, 0xed, 0x4f, + 0xc9, 0xa1, 0x9c, 0xa5, 0xd9, 0x61, 0x56, 0x2b, 0xc8, 0x4d, 0xa8, 0x1d, 0xba, 0xf6, 0xa0, 0x47, + 0xfd, 0xb9, 0x3a, 0x9f, 0x62, 0xe7, 0xb3, 0xa6, 0xd8, 0xfb, 0x9c, 0xa5, 0x7d, 0x41, 0xc2, 0xd7, + 0xc4, 0x7f, 0x1f, 0x55, 0x5d, 0x62, 0x41, 0xd5, 0xb6, 0x7a, 0x56, 0xe0, 0xf3, 0x85, 0xb3, 0xb9, + 0x74, 0x73, 0xec, 0xc7, 0x12, 0x9f, 0xe8, 0x06, 0x07, 0x13, 0x5f, 0x8d, 0xf8, 0x8d, 0x52, 0x00, + 0x9b, 0x0a, 0x7d, 0x43, 0xb7, 0xc5, 0xc2, 0xda, 0x5c, 0xfa, 0xea, 0xf8, 0x9f, 0x0d, 0x43, 0x69, + 0x4f, 0xcb, 0x67, 0xaa, 0xf0, 0xbf, 0x28, 0xb0, 0xc9, 0x2f, 0xc0, 0x4c, 0xe2, 0x6d, 0xfa, 0x73, + 0x4d, 0xde, 0x3b, 0x2f, 0x65, 0xf5, 0x4e, 0xc8, 0x15, 0xad, 0x3c, 0x89, 0x11, 0xe2, 0x63, 0x0a, + 0x8c, 0xdc, 0x81, 0xba, 0x6f, 0x99, 0xd4, 0xd0, 0x3d, 0x7f, 0x6e, 0xea, 0x34, 0xc0, 0xb3, 0x12, + 0xb8, 0xde, 0x91, 0xd5, 0x30, 0x04, 0x20, 0x0b, 0x00, 0x7d, 0xdd, 0x0b, 0x2c, 0xa1, 0xa8, 0x4e, + 0x73, 0xa5, 0x69, 0xe6, 0xe4, 0xb8, 0x05, 0x5b, 0x61, 0x29, 0xc6, 0x38, 0x18, 0x3f, 0xab, 0xbb, + 0xee, 0xf4, 0x07, 0x81, 0x58, 0x58, 0x1b, 0x82, 0xbf, 0x13, 0x96, 0x62, 0x8c, 0x83, 0xfc, 0x56, + 0x01, 0x3e, 0x15, 0xfd, 0x1d, 0xfe, 0xc8, 0x2e, 0x4c, 0xfc, 0x23, 0x6b, 0x9d, 0x1c, 0xb7, 0x3e, + 0xd5, 0x19, 0x2d, 0x12, 0x1f, 0xd7, 0x1e, 0xf2, 0x61, 0x01, 0x66, 0x06, 0x7d, 0x53, 0x0f, 0x68, + 0x27, 0x60, 0x3b, 0x9e, 0xee, 0xd1, 0xdc, 0x2c, 0x6f, 0xe2, 0xad, 0xf1, 0x67, 0xc1, 0x04, 0x5c, + 0xf4, 0x9a, 0x93, 0xe5, 0x98, 0x12, 0xab, 0xbd, 0x0d, 0xd3, 0xcb, 0x83, 0x60, 0xdf, 0xf5, 0xac, + 0x0f, 0xb8, 0xfa, 0x4f, 0xd6, 0xa0, 0x12, 0x70, 0x35, 0x4e, 0x68, 0x08, 0x9f, 0xcd, 0x7a, 0xe9, + 0x42, 0xa5, 0xbe, 0x43, 0x8f, 0x94, 0x5e, 0x22, 0x56, 0x6a, 0xa1, 0xd6, 0x89, 0xea, 0xda, 0x1f, + 0x2f, 0x40, 0xad, 0xad, 0x1b, 0x07, 0xee, 0xde, 0x1e, 0x79, 0x07, 0xea, 0x96, 0x13, 0x50, 0xef, + 0x50, 0xb7, 0x25, 0xec, 0x42, 0x0c, 0x36, 0xdc, 0x10, 0x46, 0x8f, 0xc7, 0x76, 0x5f, 0x4c, 0xd0, + 0xea, 0x40, 0xee, 0x5a, 0xb8, 0x66, 0xbc, 0x2e, 0x31, 0x30, 0x44, 0x23, 0x2d, 0xa8, 0xf8, 0x01, + 0xed, 0xfb, 0x7c, 0x0d, 0x9c, 0x16, 0xcd, 0xe8, 0xb0, 0x02, 0x14, 0xe5, 0xda, 0x5f, 0x2a, 0x40, + 0xa3, 0xad, 0xfb, 0x96, 0xc1, 0x9e, 0x92, 0xac, 0x40, 0x79, 0xe0, 0x53, 0xef, 0x6c, 0xcf, 0xc6, + 0x97, 0xad, 0x1d, 0x9f, 0x7a, 0xc8, 0x2b, 0x93, 0x7b, 0x50, 0xef, 0xeb, 0xbe, 0xff, 0xc0, 0xf5, + 0x4c, 0xb9, 0xf4, 0x9e, 0x12, 0x48, 0x6c, 0x13, 0x64, 0x55, 0x0c, 0x41, 0xb4, 0x26, 0x44, 0xba, + 0x87, 0xf6, 0x7b, 0x05, 0xb8, 0xd8, 0x1e, 0xec, 0xed, 0x51, 0x4f, 0x6a, 0xc5, 0x52, 0xdf, 0xa4, + 0x50, 0xf1, 0xa8, 0x69, 0xf9, 0xb2, 0xed, 0xab, 0x63, 0x0f, 0x14, 0x64, 0x28, 0x52, 0xbd, 0xe5, + 0xfd, 0xc5, 0x0b, 0x50, 0xa0, 0x93, 0x01, 0x34, 0xde, 0xa7, 0x6c, 0x37, 0x4e, 0xf5, 0x9e, 0x7c, + 0xba, 0xdb, 0x63, 0x8b, 0x7a, 0x8b, 0x06, 0x1d, 0x8e, 0x14, 0xd7, 0xa6, 0xc3, 0x42, 0x8c, 0x24, + 0x69, 0xbf, 0x53, 0x81, 0xa9, 0x15, 0xb7, 0xb7, 0x6b, 0x39, 0xd4, 0xbc, 0x69, 0x76, 0x29, 0x79, + 0x0f, 0xca, 0xd4, 0xec, 0x52, 0xf9, 0xb4, 0xe3, 0x2b, 0x1e, 0x0c, 0x2c, 0x52, 0x9f, 0xd8, 0x3f, + 0xe4, 0xc0, 0x64, 0x03, 0x66, 0xf6, 0x3c, 0xb7, 0x27, 0xe6, 0xf2, 0xed, 0xa3, 0xbe, 0xd4, 0x9d, + 0xdb, 0x3f, 0xa1, 0x3e, 0x9c, 0xb5, 0x04, 0xf5, 0xd1, 0x71, 0x0b, 0xa2, 0x7f, 0x98, 0xaa, 0x4b, + 0xde, 0x81, 0xb9, 0xa8, 0x24, 0x9c, 0xd4, 0x56, 0xd8, 0x76, 0x86, 0xeb, 0x4e, 0x95, 0xf6, 0xd5, + 0x93, 0xe3, 0xd6, 0xdc, 0xda, 0x08, 0x1e, 0x1c, 0x59, 0x9b, 0x4d, 0x15, 0xb3, 0x11, 0x51, 0x2c, + 0x34, 0x52, 0x65, 0x9a, 0xd0, 0x0a, 0xc6, 0xf7, 0x7d, 0x6b, 0x29, 0x11, 0x38, 0x24, 0x94, 0xac, + 0xc1, 0x54, 0xe0, 0xc6, 0xfa, 0xab, 0xc2, 0xfb, 0x4b, 0x53, 0x86, 0x8a, 0x6d, 0x77, 0x64, 0x6f, + 0x25, 0xea, 0x11, 0x84, 0xcb, 0xea, 0x7f, 0xaa, 0xa7, 0xaa, 0xbc, 0xa7, 0xe6, 0x4f, 0x8e, 0x5b, + 0x97, 0xb7, 0x33, 0x39, 0x70, 0x44, 0x4d, 0xf2, 0x2b, 0x05, 0x98, 0x51, 0x24, 0xd9, 0x47, 0xb5, + 0x49, 0xf6, 0x11, 0x61, 0x23, 0x62, 0x3b, 0x21, 0x00, 0x53, 0x02, 0xb5, 0xef, 0xd5, 0xa0, 0x11, + 0x4e, 0xf5, 0xe4, 0x33, 0x50, 0xe1, 0x26, 0x08, 0xa9, 0xc1, 0x87, 0x6b, 0x38, 0xb7, 0x54, 0xa0, + 0xa0, 0x91, 0xcf, 0x42, 0xcd, 0x70, 0x7b, 0x3d, 0xdd, 0x31, 0xb9, 0x59, 0xa9, 0xd1, 0x6e, 0x32, + 0xd5, 0x65, 0x45, 0x14, 0xa1, 0xa2, 0x91, 0xab, 0x50, 0xd6, 0xbd, 0xae, 0xb0, 0xf0, 0x34, 0xc4, + 0x7c, 0xb4, 0xec, 0x75, 0x7d, 0xe4, 0xa5, 0xe4, 0x4b, 0x50, 0xa2, 0xce, 0xe1, 0x5c, 0x79, 0xb4, + 0x6e, 0x74, 0xd3, 0x39, 0xbc, 0xaf, 0x7b, 0xed, 0xa6, 0x6c, 0x43, 0xe9, 0xa6, 0x73, 0x88, 0xac, + 0x0e, 0xd9, 0x80, 0x1a, 0x75, 0x0e, 0xd9, 0xbb, 0x97, 0xa6, 0x97, 0x4f, 0x8f, 0xa8, 0xce, 0x58, + 0xe4, 0x36, 0x21, 0xd4, 0xb0, 0x64, 0x31, 0x2a, 0x08, 0xf2, 0x75, 0x98, 0x12, 0xca, 0xd6, 0x26, + 0x7b, 0x27, 0xfe, 0x5c, 0x95, 0x43, 0xb6, 0x46, 0x6b, 0x6b, 0x9c, 0x2f, 0x32, 0x75, 0xc5, 0x0a, + 0x7d, 0x4c, 0x40, 0x91, 0xaf, 0x43, 0x43, 0xed, 0x8c, 0xd5, 0x9b, 0xcd, 0xb4, 0x12, 0xa9, 0xed, + 0x34, 0xd2, 0x6f, 0x0e, 0x2c, 0x8f, 0xf6, 0xa8, 0x13, 0xf8, 0xed, 0xe7, 0x95, 0xdd, 0x40, 0x51, + 0x7d, 0x8c, 0xd0, 0xc8, 0xee, 0xb0, 0xb9, 0x4b, 0xd8, 0x6a, 0x3e, 0x33, 0x62, 0x56, 0x1f, 0xc3, + 0xd6, 0xf5, 0x0d, 0xb8, 0x10, 0xda, 0xa3, 0xa4, 0x49, 0x43, 0x58, 0x6f, 0xbe, 0xc0, 0xaa, 0xaf, + 0x27, 0x49, 0x8f, 0x8e, 0x5b, 0x2f, 0x65, 0x18, 0x35, 0x22, 0x06, 0x4c, 0x83, 0x91, 0x0f, 0x60, + 0xc6, 0xa3, 0xba, 0x69, 0x39, 0xd4, 0xf7, 0xb7, 0x3c, 0x77, 0x37, 0xbf, 0xe6, 0xc9, 0x51, 0xc4, + 0xb0, 0xc7, 0x04, 0x32, 0xa6, 0x24, 0x91, 0x07, 0x30, 0x6d, 0x5b, 0x87, 0x34, 0x12, 0xdd, 0x9c, + 0x88, 0xe8, 0xe7, 0x4f, 0x8e, 0x5b, 0xd3, 0x1b, 0x71, 0x60, 0x4c, 0xca, 0x61, 0x9a, 0x4a, 0xdf, + 0xf5, 0x02, 0xa5, 0x9e, 0x7e, 0xfa, 0xb1, 0xea, 0xe9, 0x96, 0xeb, 0x05, 0xd1, 0x47, 0xc8, 0xfe, + 0xf9, 0x28, 0xaa, 0x6b, 0x7f, 0xad, 0x02, 0xc3, 0x9b, 0xb8, 0xe4, 0x88, 0x2b, 0x4c, 0x7a, 0xc4, + 0xa5, 0x47, 0x83, 0x58, 0x7b, 0x5e, 0x97, 0xd5, 0x26, 0x30, 0x22, 0x32, 0x46, 0x75, 0x69, 0xd2, + 0xa3, 0xfa, 0x99, 0x99, 0x78, 0x86, 0x87, 0x7f, 0xf5, 0xa3, 0x1b, 0xfe, 0xb5, 0xa7, 0x33, 0xfc, + 0xb5, 0xef, 0x96, 0x61, 0x66, 0x55, 0xa7, 0x3d, 0xd7, 0x79, 0xe2, 0x3e, 0xbe, 0xf0, 0x4c, 0xec, + 0xe3, 0x6f, 0x40, 0xdd, 0xa3, 0x7d, 0xdb, 0x32, 0x74, 0xa1, 0xae, 0x4b, 0xbb, 0x39, 0xca, 0x32, + 0x0c, 0xa9, 0x23, 0xec, 0x37, 0xa5, 0x67, 0xd2, 0x7e, 0x53, 0xfe, 0xe8, 0xed, 0x37, 0xda, 0xaf, + 0x14, 0x81, 0xab, 0xb6, 0xe4, 0x3a, 0x94, 0x99, 0xda, 0x96, 0xb6, 0x1a, 0xf2, 0xaf, 0x85, 0x53, + 0xc8, 0x3c, 0x14, 0x03, 0x57, 0x4e, 0x37, 0x20, 0xe9, 0xc5, 0x6d, 0x17, 0x8b, 0x81, 0x4b, 0x3e, + 0x00, 0x30, 0x5c, 0xc7, 0xb4, 0x94, 0x3b, 0x29, 0xdf, 0x83, 0xad, 0xb9, 0xde, 0x03, 0xdd, 0x33, + 0x57, 0x42, 0x44, 0xb1, 0x83, 0x8f, 0xfe, 0x63, 0x4c, 0x1a, 0x79, 0x03, 0xaa, 0xae, 0xb3, 0x36, + 0xb0, 0x6d, 0xde, 0xa1, 0x8d, 0xf6, 0xe7, 0x4e, 0x8e, 0x5b, 0xd5, 0x7b, 0xbc, 0xe4, 0xd1, 0x71, + 0xeb, 0x8a, 0xd8, 0x11, 0xb1, 0x7f, 0x6f, 0x7b, 0x56, 0x60, 0x39, 0xdd, 0x70, 0x43, 0x2b, 0xab, + 0x69, 0xbf, 0x5a, 0x80, 0xe6, 0x9a, 0xf5, 0x90, 0x9a, 0x6f, 0x5b, 0x8e, 0xe9, 0x3e, 0x20, 0x08, + 0x55, 0x9b, 0x3a, 0xdd, 0x60, 0x7f, 0xcc, 0x1d, 0xa7, 0xb0, 0xeb, 0x70, 0x04, 0x94, 0x48, 0x64, + 0x11, 0x1a, 0x62, 0xbf, 0x62, 0x39, 0x5d, 0xde, 0x87, 0xf5, 0x68, 0xa6, 0xef, 0x28, 0x02, 0x46, + 0x3c, 0xda, 0x11, 0x3c, 0x3f, 0xd4, 0x0d, 0xc4, 0x84, 0x72, 0xa0, 0x77, 0xd5, 0xa2, 0xb2, 0x36, + 0x76, 0x07, 0x6f, 0xeb, 0xdd, 0x58, 0xe7, 0x72, 0xad, 0x70, 0x5b, 0x67, 0x5a, 0x21, 0x43, 0xd7, + 0xfe, 0x4f, 0x01, 0xea, 0x6b, 0x03, 0xc7, 0xe0, 0x9b, 0xfa, 0x27, 0x5b, 0x93, 0x95, 0x8a, 0x59, + 0xcc, 0x54, 0x31, 0x07, 0x50, 0x3d, 0x78, 0x10, 0xaa, 0xa0, 0xcd, 0xa5, 0xcd, 0xf1, 0x47, 0x85, + 0x6c, 0xd2, 0xc2, 0x1d, 0x8e, 0x27, 0x9c, 0x9d, 0x33, 0xb2, 0x41, 0xd5, 0x3b, 0x6f, 0x73, 0xa1, + 0x52, 0xd8, 0xfc, 0x97, 0xa0, 0x19, 0x63, 0x3b, 0x93, 0xdf, 0xe3, 0xaf, 0x97, 0xa1, 0x7a, 0xab, + 0xd3, 0x59, 0xde, 0x5a, 0x27, 0xaf, 0x42, 0x53, 0xfa, 0xc1, 0xee, 0x46, 0x7d, 0x10, 0xba, 0x41, + 0x3b, 0x11, 0x09, 0xe3, 0x7c, 0x4c, 0x81, 0xf7, 0xa8, 0x6e, 0xf7, 0xe4, 0xc7, 0x12, 0xea, 0x0e, + 0xc8, 0x0a, 0x51, 0xd0, 0x88, 0x0e, 0x33, 0x03, 0x9f, 0x7a, 0xac, 0x0b, 0xc5, 0x7e, 0x5f, 0x7e, + 0x36, 0xa7, 0xb4, 0x08, 0xf0, 0x05, 0x66, 0x27, 0x01, 0x80, 0x29, 0x40, 0xf2, 0x3a, 0xd4, 0xf5, + 0x41, 0xb0, 0xcf, 0xb7, 0x5c, 0xe2, 0xdb, 0xb8, 0xca, 0xdd, 0x84, 0xb2, 0xec, 0xd1, 0x71, 0x6b, + 0xea, 0x0e, 0xb6, 0x5f, 0x55, 0xff, 0x31, 0xe4, 0x66, 0x8d, 0x53, 0x36, 0x06, 0xd9, 0xb8, 0xca, + 0x99, 0x1b, 0xb7, 0x95, 0x00, 0xc0, 0x14, 0x20, 0x79, 0x17, 0xa6, 0x0e, 0xe8, 0x51, 0xa0, 0xef, + 0x4a, 0x01, 0xd5, 0xb3, 0x08, 0x98, 0x65, 0x4a, 0xff, 0x9d, 0x58, 0x75, 0x4c, 0x80, 0x11, 0x1f, + 0x2e, 0x1d, 0x50, 0x6f, 0x97, 0x7a, 0xae, 0xb4, 0x57, 0x48, 0x21, 0xb5, 0xb3, 0x08, 0x99, 0x3b, + 0x39, 0x6e, 0x5d, 0xba, 0x93, 0x01, 0x83, 0x99, 0xe0, 0xda, 0xff, 0x2e, 0xc2, 0x85, 0x5b, 0x22, + 0x10, 0xc1, 0xf5, 0x84, 0xe6, 0x41, 0xae, 0x40, 0xc9, 0xeb, 0x0f, 0xf8, 0xc8, 0x29, 0x09, 0x57, + 0x03, 0x6e, 0xed, 0x20, 0x2b, 0x23, 0xef, 0x40, 0xdd, 0x94, 0x53, 0x86, 0x34, 0x97, 0x8c, 0x65, + 0xda, 0x52, 0xff, 0x30, 0x44, 0x63, 0x7b, 0xc3, 0x9e, 0xdf, 0xed, 0x58, 0x1f, 0x50, 0x69, 0x41, + 0xe0, 0x7b, 0xc3, 0x4d, 0x51, 0x84, 0x8a, 0xc6, 0x56, 0xd5, 0x03, 0x7a, 0x24, 0xf6, 0xcf, 0xe5, + 0x68, 0x55, 0xbd, 0x23, 0xcb, 0x30, 0xa4, 0x92, 0x96, 0xfa, 0x58, 0xd8, 0x28, 0x28, 0x0b, 0xdb, + 0xcf, 0x7d, 0x56, 0x20, 0xbf, 0x1b, 0x36, 0x65, 0xbe, 0x6f, 0x05, 0x01, 0xf5, 0xe4, 0x6b, 0x1c, + 0x6b, 0xca, 0x7c, 0x8b, 0x23, 0xa0, 0x44, 0x22, 0x3f, 0x05, 0x0d, 0x0e, 0xde, 0xb6, 0xdd, 0x5d, + 0xfe, 0xe2, 0x1a, 0xc2, 0x0a, 0x74, 0x5f, 0x15, 0x62, 0x44, 0xd7, 0xfe, 0xa0, 0x08, 0x97, 0x6f, + 0xd1, 0x40, 0x68, 0x35, 0xab, 0xb4, 0x6f, 0xbb, 0x47, 0x4c, 0x9f, 0x46, 0xfa, 0x4d, 0xf2, 0x26, + 0x80, 0xe5, 0xef, 0x76, 0x0e, 0x0d, 0xfe, 0x1d, 0x88, 0x6f, 0xf8, 0xba, 0xfc, 0x24, 0x61, 0xbd, + 0xd3, 0x96, 0x94, 0x47, 0x89, 0x7f, 0x18, 0xab, 0x13, 0x6d, 0xc8, 0x8b, 0x8f, 0xd9, 0x90, 0x77, + 0x00, 0xfa, 0x91, 0x56, 0x5e, 0xe2, 0x9c, 0x3f, 0xa3, 0xc4, 0x9c, 0x45, 0x21, 0x8f, 0xc1, 0xe4, + 0xd1, 0x93, 0x1d, 0x98, 0x35, 0xe9, 0x9e, 0x3e, 0xb0, 0x83, 0x70, 0x27, 0x21, 0x3f, 0xe2, 0xd3, + 0x6f, 0x46, 0xc2, 0x20, 0x89, 0xd5, 0x14, 0x12, 0x0e, 0x61, 0x6b, 0x7f, 0xa3, 0x04, 0xf3, 0xb7, + 0x68, 0x10, 0xda, 0xe8, 0xe4, 0xec, 0xd8, 0xe9, 0x53, 0x83, 0xbd, 0x85, 0x0f, 0x0b, 0x50, 0xb5, + 0xf5, 0x5d, 0x6a, 0xb3, 0xd5, 0x8b, 0x3d, 0xcd, 0x7b, 0x63, 0x2f, 0x04, 0xa3, 0xa5, 0x2c, 0x6c, + 0x70, 0x09, 0xa9, 0xa5, 0x41, 0x14, 0xa2, 0x14, 0xcf, 0x26, 0x75, 0xc3, 0x1e, 0xf8, 0x81, 0xd8, + 0xd9, 0x49, 0x7d, 0x32, 0x9c, 0xd4, 0x57, 0x22, 0x12, 0xc6, 0xf9, 0xc8, 0x12, 0x80, 0x61, 0x5b, + 0xd4, 0x09, 0x78, 0x2d, 0xf1, 0x5d, 0x11, 0xf5, 0x7e, 0x57, 0x42, 0x0a, 0xc6, 0xb8, 0x98, 0xa8, + 0x9e, 0xeb, 0x58, 0x81, 0x2b, 0x44, 0x95, 0x93, 0xa2, 0x36, 0x23, 0x12, 0xc6, 0xf9, 0x78, 0x35, + 0x1a, 0x78, 0x96, 0xe1, 0xf3, 0x6a, 0x95, 0x54, 0xb5, 0x88, 0x84, 0x71, 0x3e, 0xb6, 0xe6, 0xc5, + 0x9e, 0xff, 0x4c, 0x6b, 0xde, 0x6f, 0x36, 0xe0, 0x5a, 0xa2, 0x5b, 0x03, 0x3d, 0xa0, 0x7b, 0x03, + 0xbb, 0x43, 0x03, 0xf5, 0x02, 0xc7, 0x5c, 0x0b, 0xff, 0x54, 0xf4, 0xde, 0x45, 0xf8, 0x93, 0x31, + 0x99, 0xf7, 0x3e, 0xd4, 0xc0, 0x53, 0xbd, 0xfb, 0x45, 0x68, 0x38, 0x7a, 0xe0, 0xf3, 0x0f, 0x57, + 0x7e, 0xa3, 0xa1, 0x1a, 0x76, 0x57, 0x11, 0x30, 0xe2, 0x21, 0x5b, 0x70, 0x49, 0x76, 0xf1, 0xcd, + 0x87, 0x6c, 0xcf, 0x4f, 0x3d, 0x51, 0x57, 0x2e, 0xa7, 0xb2, 0xee, 0xa5, 0xcd, 0x0c, 0x1e, 0xcc, + 0xac, 0x49, 0x36, 0xe1, 0xa2, 0x21, 0x42, 0x42, 0xa8, 0xed, 0xea, 0xa6, 0x02, 0x14, 0x26, 0xd1, + 0x70, 0x6b, 0xb4, 0x32, 0xcc, 0x82, 0x59, 0xf5, 0xd2, 0xa3, 0xb9, 0x3a, 0xd6, 0x68, 0xae, 0x8d, + 0x33, 0x9a, 0xeb, 0xe3, 0x8d, 0xe6, 0xc6, 0xe9, 0x46, 0x33, 0xeb, 0x79, 0x36, 0x8e, 0xa8, 0xc7, + 0xd4, 0x13, 0xb1, 0xc2, 0xc6, 0x22, 0x8e, 0xc2, 0x9e, 0xef, 0x64, 0xf0, 0x60, 0x66, 0x4d, 0xb2, + 0x0b, 0xf3, 0xa2, 0xfc, 0xa6, 0x63, 0x78, 0x47, 0x7d, 0xb6, 0xf0, 0xc4, 0x70, 0x9b, 0x09, 0x9b, + 0xf4, 0x7c, 0x67, 0x24, 0x27, 0x3e, 0x06, 0x85, 0x7c, 0x19, 0xa6, 0xc5, 0x5b, 0xda, 0xd4, 0xfb, + 0x1c, 0x56, 0xc4, 0x1f, 0xbd, 0x20, 0x61, 0xa7, 0x57, 0xe2, 0x44, 0x4c, 0xf2, 0x92, 0x65, 0xb8, + 0xd0, 0x3f, 0x34, 0xd8, 0xcf, 0xf5, 0xbd, 0xbb, 0x94, 0x9a, 0xd4, 0xe4, 0x0e, 0xcf, 0x46, 0xfb, + 0x45, 0x65, 0xdd, 0xd9, 0x4a, 0x92, 0x31, 0xcd, 0x4f, 0x5e, 0x87, 0x29, 0x3f, 0xd0, 0xbd, 0x40, + 0x1a, 0x82, 0xe7, 0x66, 0x44, 0x7c, 0x96, 0xb2, 0x93, 0x76, 0x62, 0x34, 0x4c, 0x70, 0x66, 0xae, + 0x17, 0x17, 0xce, 0x6f, 0xbd, 0xc8, 0x33, 0x5b, 0xfd, 0x83, 0x22, 0x5c, 0xbf, 0x45, 0x83, 0x4d, + 0xd7, 0x91, 0x66, 0xf4, 0xac, 0x65, 0xff, 0x54, 0x56, 0xf4, 0xe4, 0xa2, 0x5d, 0x9c, 0xe8, 0xa2, + 0x5d, 0x9a, 0xd0, 0xa2, 0x5d, 0x3e, 0xc7, 0x45, 0xfb, 0x6f, 0x15, 0xe1, 0xc5, 0x44, 0x4f, 0x6e, + 0xb9, 0xa6, 0x9a, 0xf0, 0x3f, 0xe9, 0xc0, 0x53, 0x74, 0xe0, 0x23, 0xa1, 0x77, 0x72, 0x47, 0x68, + 0x4a, 0xe3, 0xf9, 0x4e, 0x5a, 0xe3, 0x79, 0x37, 0xcf, 0xca, 0x97, 0x21, 0xe1, 0x54, 0x2b, 0xde, + 0x5b, 0x40, 0x3c, 0xe9, 0xb6, 0x8d, 0xcc, 0xd9, 0x52, 0xe9, 0x09, 0x03, 0x40, 0x71, 0x88, 0x03, + 0x33, 0x6a, 0x91, 0x0e, 0xbc, 0xe0, 0x53, 0x27, 0xb0, 0x1c, 0x6a, 0x27, 0xe1, 0x84, 0x36, 0xf4, + 0x92, 0x84, 0x7b, 0xa1, 0x93, 0xc5, 0x84, 0xd9, 0x75, 0xf3, 0xcc, 0x03, 0xff, 0x04, 0xb8, 0xca, + 0x29, 0xba, 0x66, 0x62, 0x1a, 0xcb, 0x87, 0x69, 0x8d, 0xe5, 0xbd, 0xfc, 0xef, 0x6d, 0x3c, 0x6d, + 0x65, 0x09, 0x80, 0xbf, 0x85, 0xb8, 0xba, 0x12, 0x2e, 0xd2, 0x18, 0x52, 0x30, 0xc6, 0xc5, 0x16, + 0x20, 0xd5, 0xcf, 0x71, 0x4d, 0x25, 0x5c, 0x80, 0x3a, 0x71, 0x22, 0x26, 0x79, 0x47, 0x6a, 0x3b, + 0x95, 0xb1, 0xb5, 0x9d, 0xb7, 0x80, 0x24, 0x0c, 0x8f, 0x02, 0xaf, 0x9a, 0x8c, 0x3f, 0x5e, 0x1f, + 0xe2, 0xc0, 0x8c, 0x5a, 0x23, 0x86, 0x72, 0x6d, 0xb2, 0x43, 0xb9, 0x3e, 0xfe, 0x50, 0x26, 0xef, + 0xc1, 0x15, 0x2e, 0x4a, 0xf6, 0x4f, 0x12, 0x58, 0xe8, 0x3d, 0x9f, 0x96, 0xc0, 0x57, 0x70, 0x14, + 0x23, 0x8e, 0xc6, 0x60, 0xef, 0xc7, 0xf0, 0xa8, 0xc9, 0x84, 0xeb, 0xf6, 0x68, 0x9d, 0x68, 0x25, + 0x83, 0x07, 0x33, 0x6b, 0xb2, 0x21, 0x16, 0xb0, 0x61, 0xa8, 0xef, 0xda, 0xd4, 0x94, 0xf1, 0xd7, + 0xe1, 0x10, 0xdb, 0xde, 0xe8, 0x48, 0x0a, 0xc6, 0xb8, 0xb2, 0xd4, 0x94, 0xa9, 0x33, 0xaa, 0x29, + 0xb7, 0xb8, 0x95, 0x7e, 0x2f, 0xa1, 0x0d, 0x49, 0x5d, 0x27, 0x8c, 0xa8, 0x5f, 0x49, 0x33, 0xe0, + 0x70, 0x1d, 0xae, 0x25, 0x1a, 0x9e, 0xd5, 0x0f, 0xfc, 0x24, 0xd6, 0x4c, 0x4a, 0x4b, 0xcc, 0xe0, + 0xc1, 0xcc, 0x9a, 0x4c, 0x3f, 0xdf, 0xa7, 0xba, 0x1d, 0xec, 0x27, 0x01, 0x2f, 0x24, 0xf5, 0xf3, + 0xdb, 0xc3, 0x2c, 0x98, 0x55, 0x2f, 0x73, 0x41, 0x9a, 0x7d, 0x36, 0xd5, 0xaa, 0x6f, 0x97, 0xe0, + 0xca, 0x2d, 0x1a, 0x84, 0xa1, 0x69, 0x9f, 0x98, 0x51, 0x3e, 0x02, 0x33, 0xca, 0x6f, 0x54, 0xe0, + 0xe2, 0x2d, 0x1a, 0x0c, 0x69, 0x63, 0xff, 0x9f, 0x76, 0xff, 0x26, 0x5c, 0x8c, 0xa2, 0x21, 0x3b, + 0x81, 0xeb, 0x89, 0xb5, 0x3c, 0xb5, 0x5b, 0xee, 0x0c, 0xb3, 0x60, 0x56, 0x3d, 0xf2, 0x75, 0x78, + 0x91, 0x2f, 0xf5, 0x4e, 0x57, 0xd8, 0x67, 0x85, 0x31, 0x21, 0x76, 0x9e, 0xa7, 0x25, 0x21, 0x5f, + 0xec, 0x64, 0xb3, 0xe1, 0xa8, 0xfa, 0xe4, 0x5b, 0x30, 0xd5, 0xb7, 0xfa, 0xd4, 0xb6, 0x1c, 0xae, + 0x9f, 0xe5, 0x0e, 0x22, 0xda, 0x8a, 0x81, 0x45, 0x1b, 0xb8, 0x78, 0x29, 0x26, 0x04, 0x66, 0x8e, + 0xd4, 0xfa, 0x39, 0x8e, 0xd4, 0xff, 0x5e, 0x84, 0xda, 0x2d, 0xcf, 0x1d, 0xf4, 0xdb, 0x47, 0xa4, + 0x0b, 0xd5, 0x07, 0xdc, 0x79, 0x26, 0x5d, 0x53, 0xe3, 0x9f, 0x28, 0x10, 0x3e, 0xb8, 0x48, 0x25, + 0x12, 0xff, 0x51, 0xc2, 0xb3, 0x41, 0x7c, 0x40, 0x8f, 0xa8, 0x29, 0x7d, 0x68, 0xe1, 0x20, 0xbe, + 0xc3, 0x0a, 0x51, 0xd0, 0x48, 0x0f, 0x2e, 0xe8, 0xb6, 0xed, 0x3e, 0xa0, 0xe6, 0x86, 0x1e, 0x70, + 0xbf, 0xb7, 0xf4, 0xad, 0x9c, 0xd5, 0x2c, 0xcd, 0x83, 0x19, 0x96, 0x93, 0x50, 0x98, 0xc6, 0x26, + 0xef, 0x43, 0xcd, 0x0f, 0x5c, 0x4f, 0x29, 0x5b, 0xcd, 0xa5, 0x95, 0xf1, 0x5f, 0x7a, 0xfb, 0x6b, + 0x1d, 0x01, 0x25, 0x6c, 0xf6, 0xf2, 0x0f, 0x2a, 0x01, 0xda, 0xaf, 0x17, 0x00, 0x6e, 0x6f, 0x6f, + 0x6f, 0x49, 0xf7, 0x82, 0x09, 0x65, 0x7d, 0x10, 0x3a, 0x2a, 0xc7, 0x77, 0x08, 0x26, 0x02, 0x79, + 0xa5, 0x0f, 0x6f, 0x10, 0xec, 0x23, 0x47, 0x27, 0x3f, 0x09, 0x35, 0xa9, 0x20, 0xcb, 0x6e, 0x0f, + 0xe3, 0x29, 0xa4, 0x12, 0x8d, 0x8a, 0xae, 0xfd, 0x76, 0x11, 0x60, 0xdd, 0xb4, 0x69, 0x47, 0x1d, + 0x02, 0x69, 0x04, 0xfb, 0x1e, 0xf5, 0xf7, 0x5d, 0xdb, 0x1c, 0xd3, 0x9b, 0xca, 0x6d, 0xfe, 0xdb, + 0x0a, 0x04, 0x23, 0x3c, 0x62, 0xc2, 0x94, 0x1f, 0xd0, 0xbe, 0x8a, 0xed, 0x1d, 0xd3, 0x89, 0x32, + 0x2b, 0xec, 0x22, 0x11, 0x0e, 0x26, 0x50, 0x89, 0x0e, 0x4d, 0xcb, 0x31, 0xc4, 0x07, 0xd2, 0x3e, + 0x1a, 0x73, 0x20, 0x5d, 0x60, 0x3b, 0x8e, 0xf5, 0x08, 0x06, 0xe3, 0x98, 0xda, 0xef, 0x16, 0xe1, + 0x32, 0x97, 0xc7, 0x9a, 0x91, 0x88, 0xe0, 0x25, 0x7f, 0x74, 0xe8, 0xc0, 0xea, 0x1f, 0x3e, 0x9d, + 0x68, 0x71, 0xde, 0x71, 0x93, 0x06, 0x7a, 0xa4, 0xcf, 0x45, 0x65, 0xb1, 0x53, 0xaa, 0x03, 0x28, + 0xfb, 0x6c, 0xbe, 0x12, 0xbd, 0xd7, 0x19, 0x7b, 0x08, 0x65, 0x3f, 0x00, 0x9f, 0xbd, 0x42, 0xaf, + 0x31, 0x9f, 0xb5, 0xb8, 0x38, 0xf2, 0x4b, 0x50, 0xf5, 0x03, 0x3d, 0x18, 0xa8, 0x4f, 0x73, 0x67, + 0xd2, 0x82, 0x39, 0x78, 0x34, 0x8f, 0x88, 0xff, 0x28, 0x85, 0x6a, 0xbf, 0x5b, 0x80, 0xf9, 0xec, + 0x8a, 0x1b, 0x96, 0x1f, 0x90, 0x3f, 0x32, 0xd4, 0xed, 0xa7, 0x7c, 0xe3, 0xac, 0x36, 0xef, 0xf4, + 0xf0, 0x4c, 0x83, 0x2a, 0x89, 0x75, 0x79, 0x00, 0x15, 0x2b, 0xa0, 0x3d, 0xb5, 0xbf, 0xbc, 0x37, + 0xe1, 0x47, 0x8f, 0x2d, 0xed, 0x4c, 0x0a, 0x0a, 0x61, 0xda, 0x77, 0x8b, 0xa3, 0x1e, 0x99, 0x2f, + 0x1f, 0x76, 0x32, 0x4a, 0xfc, 0x4e, 0xbe, 0x28, 0xf1, 0x64, 0x83, 0x86, 0x83, 0xc5, 0xff, 0xd8, + 0x70, 0xb0, 0xf8, 0xbd, 0xfc, 0xc1, 0xe2, 0xa9, 0x6e, 0x18, 0x19, 0x33, 0xfe, 0xc3, 0x12, 0x5c, + 0x7d, 0xdc, 0xb0, 0x61, 0xeb, 0x99, 0x1c, 0x9d, 0x79, 0xd7, 0xb3, 0xc7, 0x8f, 0x43, 0xb2, 0x04, + 0x95, 0xfe, 0xbe, 0xee, 0x2b, 0xa5, 0xec, 0x6a, 0x18, 0x66, 0xc8, 0x0a, 0x1f, 0xb1, 0x49, 0x83, + 0x2b, 0x73, 0xfc, 0x2f, 0x0a, 0x56, 0x36, 0x1d, 0xf7, 0xa8, 0xef, 0x47, 0x36, 0x81, 0x70, 0x3a, + 0xde, 0x14, 0xc5, 0xa8, 0xe8, 0x24, 0x80, 0xaa, 0x30, 0x31, 0xcb, 0x95, 0x69, 0xfc, 0x40, 0xae, + 0x8c, 0x83, 0x05, 0xd1, 0x43, 0x49, 0x6f, 0x85, 0x94, 0x45, 0x16, 0xa0, 0x1c, 0x44, 0x61, 0xde, + 0x6a, 0x6b, 0x5e, 0xce, 0xd0, 0x4f, 0x39, 0x1f, 0xdb, 0xd8, 0xbb, 0xbb, 0xdc, 0xa8, 0x6e, 0x4a, + 0xff, 0xb9, 0xe5, 0x3a, 0x5c, 0x21, 0x2b, 0x45, 0x1b, 0xfb, 0x7b, 0x43, 0x1c, 0x98, 0x51, 0x4b, + 0xfb, 0xe7, 0x75, 0xb8, 0x9c, 0x3d, 0x1e, 0x58, 0xbf, 0x1d, 0x52, 0xcf, 0x67, 0xd8, 0x85, 0x64, + 0xbf, 0xdd, 0x17, 0xc5, 0xa8, 0xe8, 0x1f, 0xeb, 0x80, 0xb3, 0xdf, 0x28, 0xc0, 0x15, 0x4f, 0xfa, + 0x88, 0x9e, 0x46, 0xd0, 0xd9, 0x4b, 0xc2, 0x9c, 0x31, 0x42, 0x20, 0x8e, 0x6e, 0x0b, 0xf9, 0x2b, + 0x05, 0x98, 0xeb, 0xa5, 0xec, 0x1c, 0xe7, 0x78, 0xe6, 0x92, 0x9f, 0xa3, 0xd8, 0x1c, 0x21, 0x0f, + 0x47, 0xb6, 0x84, 0x7c, 0x0b, 0x9a, 0x7d, 0x36, 0x2e, 0xfc, 0x80, 0x3a, 0x86, 0x0a, 0x10, 0x1d, + 0xff, 0x4b, 0xda, 0x8a, 0xb0, 0xc2, 0x33, 0x57, 0x5c, 0x3f, 0x88, 0x11, 0x30, 0x2e, 0xf1, 0x19, + 0x3f, 0x64, 0x79, 0x03, 0xea, 0x3e, 0x0d, 0x02, 0xcb, 0xe9, 0x8a, 0xfd, 0x46, 0x43, 0x7c, 0x2b, + 0x1d, 0x59, 0x86, 0x21, 0x95, 0xfc, 0x14, 0x34, 0xb8, 0xcb, 0x69, 0xd9, 0xeb, 0xfa, 0x73, 0x0d, + 0x1e, 0x2e, 0x36, 0x2d, 0x02, 0xe0, 0x64, 0x21, 0x46, 0x74, 0xf2, 0x05, 0x98, 0xda, 0xe5, 0x9f, + 0xaf, 0x3c, 0x77, 0x2f, 0x6c, 0x5c, 0x5c, 0x5b, 0x6b, 0xc7, 0xca, 0x31, 0xc1, 0x45, 0x96, 0x00, + 0x68, 0xe8, 0x97, 0x4b, 0xdb, 0xb3, 0x22, 0x8f, 0x1d, 0xc6, 0xb8, 0xc8, 0x4b, 0x50, 0x0a, 0x6c, + 0x9f, 0xdb, 0xb0, 0xea, 0xd1, 0x16, 0x74, 0x7b, 0xa3, 0x83, 0xac, 0x5c, 0xfb, 0x83, 0x02, 0x5c, + 0x48, 0x1d, 0x47, 0x62, 0x55, 0x06, 0x9e, 0x2d, 0xa7, 0x91, 0xb0, 0xca, 0x0e, 0x6e, 0x20, 0x2b, + 0x27, 0xef, 0x49, 0xb5, 0xbc, 0x98, 0x33, 0xc5, 0xc8, 0x5d, 0x3d, 0xf0, 0x99, 0x1e, 0x3e, 0xa4, + 0x91, 0x73, 0x37, 0x5f, 0xd4, 0x1e, 0xb9, 0x0e, 0xc4, 0xdc, 0x7c, 0x11, 0x0d, 0x13, 0x9c, 0x29, + 0x83, 0x5f, 0xf9, 0x34, 0x06, 0x3f, 0xed, 0x57, 0x8b, 0xb1, 0x1e, 0x90, 0x9a, 0xfd, 0x13, 0x7a, + 0xe0, 0x65, 0xb6, 0x80, 0x86, 0x8b, 0x7b, 0x23, 0xbe, 0xfe, 0xf1, 0xc5, 0x58, 0x52, 0xc9, 0xdb, + 0xa2, 0xef, 0x4b, 0x39, 0x0f, 0x72, 0x6f, 0x6f, 0x74, 0x44, 0x74, 0x95, 0x7a, 0x6b, 0xe1, 0x2b, + 0x28, 0x9f, 0xd3, 0x2b, 0xd0, 0xfe, 0x51, 0x09, 0x9a, 0x6f, 0xb9, 0xbb, 0x1f, 0x93, 0x08, 0xea, + 0xec, 0x65, 0xaa, 0xf8, 0x11, 0x2e, 0x53, 0x3b, 0xf0, 0x62, 0x10, 0xd8, 0x1d, 0x6a, 0xb8, 0x8e, + 0xe9, 0x2f, 0xef, 0x05, 0xd4, 0x5b, 0xb3, 0x1c, 0xcb, 0xdf, 0xa7, 0xa6, 0x74, 0x27, 0x7d, 0xea, + 0xe4, 0xb8, 0xf5, 0xe2, 0xf6, 0xf6, 0x46, 0x16, 0x0b, 0x8e, 0xaa, 0xcb, 0xa7, 0x0d, 0x71, 0x76, + 0x94, 0x9f, 0xad, 0x92, 0x31, 0x37, 0x62, 0xda, 0x88, 0x95, 0x63, 0x82, 0x4b, 0xfb, 0xb7, 0x45, + 0x68, 0x84, 0xc9, 0x23, 0xc8, 0x67, 0xa1, 0xb6, 0xeb, 0xb9, 0x07, 0xd4, 0x13, 0x9e, 0x3b, 0x79, + 0xb6, 0xaa, 0x2d, 0x8a, 0x50, 0xd1, 0xc8, 0x67, 0xa0, 0x12, 0xb8, 0x7d, 0xcb, 0x48, 0x1b, 0xd4, + 0xb6, 0x59, 0x21, 0x0a, 0x1a, 0xff, 0x10, 0x78, 0x58, 0x21, 0x7f, 0xaa, 0x7a, 0xec, 0x43, 0xe0, + 0xa5, 0x28, 0xa9, 0xea, 0x43, 0x28, 0x4f, 0xfc, 0x43, 0x78, 0x39, 0x54, 0x01, 0x2b, 0xc9, 0x2f, + 0x31, 0xa5, 0xb4, 0xbd, 0x0b, 0x65, 0x5f, 0xf7, 0x6d, 0xb9, 0xbc, 0xe5, 0xc8, 0xd7, 0xb0, 0xdc, + 0xd9, 0x90, 0xf9, 0x1a, 0x96, 0x3b, 0x1b, 0xc8, 0x41, 0xb5, 0xdf, 0x2e, 0x41, 0x53, 0xf4, 0xaf, + 0x98, 0x3d, 0x26, 0xd9, 0xc3, 0x6f, 0xf0, 0x90, 0x0b, 0x7f, 0xd0, 0xa3, 0x1e, 0x37, 0x47, 0xc9, + 0xc9, 0x30, 0xee, 0x47, 0x88, 0x88, 0x61, 0xd8, 0x45, 0x54, 0xf4, 0xe3, 0xdd, 0xf5, 0x6c, 0xa9, + 0xe0, 0x09, 0x50, 0xa4, 0x8e, 0x2b, 0x23, 0x29, 0xc3, 0xa5, 0xe2, 0x4e, 0x8c, 0x86, 0x09, 0x4e, + 0xed, 0xbf, 0x15, 0xa1, 0xb1, 0x61, 0xed, 0x51, 0xe3, 0xc8, 0xb0, 0x29, 0xf9, 0x06, 0xcc, 0x9b, + 0xd4, 0xa6, 0x6c, 0xc5, 0xbc, 0xe5, 0xe9, 0x06, 0xdd, 0xa2, 0x9e, 0xc5, 0x13, 0x38, 0xb1, 0x6f, + 0x50, 0x06, 0xb8, 0x5e, 0x3b, 0x39, 0x6e, 0xcd, 0xaf, 0x8e, 0xe4, 0xc2, 0xc7, 0x20, 0x90, 0x75, + 0x98, 0x32, 0xa9, 0x6f, 0x79, 0xd4, 0xdc, 0x8a, 0x6d, 0x88, 0x3e, 0xab, 0xda, 0xb9, 0x1a, 0xa3, + 0x3d, 0x3a, 0x6e, 0x4d, 0x2b, 0x43, 0xa8, 0xd8, 0x19, 0x25, 0xaa, 0xb2, 0xa9, 0xa5, 0xaf, 0x0f, + 0x7c, 0x9a, 0xd1, 0xce, 0x12, 0x6f, 0x27, 0x9f, 0x5a, 0xb6, 0xb2, 0x59, 0x70, 0x54, 0x5d, 0xb2, + 0x0b, 0x73, 0xbc, 0xfd, 0x59, 0xb8, 0x65, 0x8e, 0xfb, 0xf2, 0xc9, 0x71, 0x4b, 0x5b, 0xa5, 0x7d, + 0x8f, 0x1a, 0x7a, 0x40, 0xcd, 0xd5, 0x11, 0xdc, 0x38, 0x12, 0x47, 0xab, 0x40, 0x69, 0xc3, 0xed, + 0x6a, 0xdf, 0x2d, 0x41, 0x98, 0x51, 0x8c, 0xfc, 0xc9, 0x02, 0x34, 0x75, 0xc7, 0x71, 0x03, 0x99, + 0xad, 0x4b, 0x44, 0x13, 0x60, 0xee, 0xc4, 0x65, 0x0b, 0xcb, 0x11, 0xa8, 0x70, 0x44, 0x87, 0xce, + 0xf1, 0x18, 0x05, 0xe3, 0xb2, 0xc9, 0x20, 0xe5, 0x1b, 0xdf, 0xcc, 0xdf, 0x8a, 0x53, 0x78, 0xc2, + 0xe7, 0xbf, 0x0a, 0xb3, 0xe9, 0xc6, 0x9e, 0xc5, 0xb5, 0x95, 0x2b, 0xc8, 0xa0, 0x08, 0x10, 0xc5, + 0xc7, 0x3c, 0x05, 0x83, 0x9c, 0x95, 0x30, 0xc8, 0x8d, 0x9f, 0xd6, 0x21, 0x6a, 0xf4, 0x48, 0x23, + 0xdc, 0x37, 0x53, 0x46, 0xb8, 0xf5, 0x49, 0x08, 0x7b, 0xbc, 0xe1, 0x6d, 0x17, 0x2e, 0x46, 0xbc, + 0xd1, 0xec, 0x72, 0x27, 0xf5, 0xf5, 0x0b, 0xbd, 0xf2, 0x73, 0x23, 0xbe, 0xfe, 0x0b, 0xb1, 0x80, + 0xa5, 0xe1, 0xef, 0x5f, 0xfb, 0xab, 0x05, 0x98, 0x8d, 0x0b, 0xe1, 0x67, 0xd0, 0xbf, 0x08, 0xd3, + 0x1e, 0xd5, 0xcd, 0xb6, 0x1e, 0x18, 0xfb, 0x3c, 0x34, 0xbe, 0xc0, 0x63, 0xd9, 0xf9, 0x69, 0x39, + 0x8c, 0x13, 0x30, 0xc9, 0x47, 0x74, 0x68, 0xb2, 0x82, 0x6d, 0xab, 0x47, 0xdd, 0x41, 0x30, 0xa6, + 0x95, 0x99, 0x6f, 0xf0, 0x30, 0x82, 0xc1, 0x38, 0xa6, 0xf6, 0xc3, 0x02, 0xcc, 0xc4, 0x1b, 0x7c, + 0xee, 0x16, 0xc8, 0xfd, 0xa4, 0x05, 0x72, 0x65, 0x02, 0xef, 0x7d, 0x84, 0xd5, 0xf1, 0xdb, 0xcd, + 0xf8, 0xa3, 0x71, 0x4b, 0x63, 0xdc, 0xb8, 0x52, 0x78, 0xac, 0x71, 0xe5, 0xe3, 0x9f, 0xa8, 0x6a, + 0xd4, 0xae, 0xa0, 0xfc, 0x0c, 0xef, 0x0a, 0x3e, 0xca, 0x6c, 0x57, 0xb1, 0x8c, 0x4d, 0xd5, 0x1c, + 0x19, 0x9b, 0x7a, 0x61, 0xc6, 0xa6, 0xda, 0xc4, 0x26, 0xb6, 0xd3, 0x64, 0x6d, 0xaa, 0x3f, 0xd5, + 0xac, 0x4d, 0x8d, 0xf3, 0xca, 0xda, 0x04, 0x79, 0xb3, 0x36, 0x7d, 0xa7, 0x00, 0x33, 0x66, 0xe2, + 0x84, 0xb1, 0x3c, 0xdb, 0x3f, 0xfe, 0x72, 0x96, 0x3c, 0xb0, 0x2c, 0x8e, 0x98, 0x25, 0xcb, 0x30, + 0x25, 0x32, 0x2b, 0x57, 0xd2, 0xd4, 0x47, 0x92, 0x2b, 0x89, 0xfc, 0x12, 0x34, 0x6c, 0xb5, 0xd6, + 0xc9, 0x0c, 0x92, 0x1b, 0x13, 0x19, 0x92, 0x12, 0x33, 0x3a, 0xc5, 0x10, 0x16, 0x61, 0x24, 0x51, + 0xfb, 0x5f, 0xb5, 0xf8, 0x82, 0xf8, 0xb4, 0x7d, 0x1c, 0xaf, 0x25, 0x7d, 0x1c, 0xd7, 0xd3, 0x3e, + 0x8e, 0xa1, 0xd5, 0x5c, 0xfa, 0x39, 0x3e, 0x1f, 0x5b, 0x27, 0x4a, 0x3c, 0x49, 0x53, 0x38, 0xe4, + 0x32, 0xd6, 0x8a, 0x65, 0xb8, 0x20, 0x95, 0x00, 0x45, 0xe4, 0x93, 0xec, 0x74, 0x14, 0x95, 0xb6, + 0x9a, 0x24, 0x63, 0x9a, 0x9f, 0x09, 0xf4, 0x55, 0xae, 0x5e, 0xb1, 0x63, 0x8b, 0xc6, 0xb8, 0xca, + 0xa3, 0x1b, 0x72, 0xb0, 0xdd, 0x9d, 0x47, 0x75, 0x5f, 0x7a, 0x2a, 0x62, 0xbb, 0x3b, 0xe4, 0xa5, + 0x28, 0xa9, 0x71, 0x77, 0x4d, 0xed, 0x09, 0xee, 0x1a, 0x1d, 0x9a, 0xb6, 0xee, 0x07, 0x62, 0x30, + 0x99, 0x72, 0x36, 0xf9, 0x43, 0xa7, 0x5b, 0xf7, 0x99, 0x2e, 0x11, 0x29, 0xf0, 0x1b, 0x11, 0x0c, + 0xc6, 0x31, 0x89, 0x09, 0x53, 0xec, 0x2f, 0x9f, 0x59, 0xcc, 0xe5, 0x40, 0x66, 0xb4, 0x3b, 0x8b, + 0x8c, 0x70, 0xeb, 0xb8, 0x11, 0xc3, 0xc1, 0x04, 0xea, 0x08, 0x8f, 0x0e, 0x8c, 0xe3, 0xd1, 0x21, + 0x5f, 0x16, 0x8a, 0xdb, 0x51, 0xf8, 0x5a, 0x9b, 0xfc, 0xb5, 0x86, 0x11, 0xad, 0x18, 0x27, 0x62, + 0x92, 0x97, 0x8d, 0x8a, 0x81, 0xec, 0x06, 0x55, 0x7d, 0x2a, 0x39, 0x2a, 0x76, 0x92, 0x64, 0x4c, + 0xf3, 0x93, 0x2d, 0xb8, 0x14, 0x16, 0xc5, 0x9b, 0x31, 0xcd, 0x71, 0xc2, 0x10, 0xc3, 0x9d, 0x0c, + 0x1e, 0xcc, 0xac, 0xc9, 0xcf, 0xec, 0x0c, 0x3c, 0x8f, 0x3a, 0xc1, 0x6d, 0xdd, 0xdf, 0x97, 0xb1, + 0x8a, 0xd1, 0x99, 0x9d, 0x88, 0x84, 0x71, 0x3e, 0xb2, 0x04, 0x20, 0xe0, 0x78, 0xad, 0x0b, 0xc9, + 0x70, 0xe0, 0x9d, 0x90, 0x82, 0x31, 0x2e, 0xed, 0x3b, 0x0d, 0x68, 0xde, 0xd5, 0x03, 0xeb, 0x90, + 0x72, 0xf7, 0xeb, 0xf9, 0xf8, 0xc0, 0xfe, 0x42, 0x01, 0x2e, 0x27, 0x63, 0x6c, 0xcf, 0xd1, 0x11, + 0xc6, 0x73, 0x3c, 0x61, 0xa6, 0x34, 0x1c, 0xd1, 0x0a, 0xee, 0x12, 0x1b, 0x0a, 0xd9, 0x3d, 0x6f, + 0x97, 0x58, 0x67, 0x94, 0x40, 0x1c, 0xdd, 0x96, 0x8f, 0x8b, 0x4b, 0xec, 0xd9, 0x4e, 0x4a, 0x9a, + 0x72, 0xd8, 0xd5, 0x9e, 0x19, 0x87, 0x5d, 0xfd, 0x99, 0xd0, 0xfa, 0xfb, 0x31, 0x87, 0x5d, 0x23, + 0x67, 0xe0, 0x98, 0x3c, 0x96, 0x22, 0xd0, 0x46, 0x39, 0xfe, 0x78, 0x46, 0x09, 0xe5, 0x48, 0x61, + 0xca, 0xf2, 0xae, 0xee, 0x5b, 0x86, 0x54, 0x3b, 0x72, 0x24, 0x61, 0x56, 0xc9, 0x19, 0x45, 0x7c, + 0x09, 0xff, 0x8b, 0x02, 0x3b, 0xca, 0x45, 0x59, 0xcc, 0x95, 0x8b, 0x92, 0xac, 0x40, 0xd9, 0x39, + 0xa0, 0x47, 0x67, 0xcb, 0xcd, 0xc0, 0x37, 0x81, 0x77, 0xef, 0xd0, 0x23, 0xe4, 0x95, 0xb5, 0xef, + 0x15, 0x01, 0xd8, 0xe3, 0x9f, 0xce, 0x75, 0xf6, 0x93, 0x50, 0xf3, 0x07, 0xdc, 0x30, 0x24, 0x15, + 0xa6, 0x28, 0xda, 0x4e, 0x14, 0xa3, 0xa2, 0x93, 0xcf, 0x40, 0xe5, 0x9b, 0x03, 0x3a, 0x50, 0x71, + 0x20, 0xe1, 0xbe, 0xe1, 0x6b, 0xac, 0x10, 0x05, 0xed, 0xfc, 0xcc, 0xdb, 0xca, 0xc5, 0x56, 0x39, + 0x2f, 0x17, 0x5b, 0x03, 0x6a, 0x77, 0x5d, 0x1e, 0xbc, 0xab, 0xfd, 0xe7, 0x22, 0x40, 0x14, 0x1c, + 0x49, 0x7e, 0xbd, 0x00, 0x2f, 0x84, 0x1f, 0x5c, 0x20, 0xb6, 0x7f, 0x3c, 0xef, 0x79, 0x6e, 0x77, + 0x5b, 0xd6, 0xc7, 0xce, 0x67, 0xa0, 0xad, 0x2c, 0x71, 0x98, 0xdd, 0x0a, 0x82, 0x50, 0xa7, 0xbd, + 0x7e, 0x70, 0xb4, 0x6a, 0x79, 0x72, 0x04, 0x66, 0xc6, 0xe0, 0xde, 0x94, 0x3c, 0xa2, 0xaa, 0xb4, + 0x51, 0xf0, 0x8f, 0x48, 0x51, 0x30, 0xc4, 0x21, 0xfb, 0x50, 0x77, 0xdc, 0xf7, 0x7c, 0xd6, 0x1d, + 0x72, 0x38, 0xbe, 0x39, 0x7e, 0x97, 0x8b, 0x6e, 0x15, 0x6e, 0x17, 0xf9, 0x07, 0x6b, 0x8e, 0xec, + 0xec, 0x5f, 0x2b, 0xc2, 0xc5, 0x8c, 0x7e, 0x20, 0x6f, 0xc2, 0xac, 0x8c, 0x43, 0x8d, 0x2e, 0x00, + 0x28, 0x44, 0x17, 0x00, 0x74, 0x52, 0x34, 0x1c, 0xe2, 0x26, 0xef, 0x01, 0xe8, 0x86, 0x41, 0x7d, + 0x7f, 0xd3, 0x35, 0xd5, 0x7e, 0xe0, 0x0d, 0xa6, 0xbe, 0x2c, 0x87, 0xa5, 0x8f, 0x8e, 0x5b, 0x3f, + 0x9d, 0x15, 0x5a, 0x9e, 0xea, 0xe7, 0xa8, 0x02, 0xc6, 0x20, 0xc9, 0x37, 0x00, 0x84, 0x0d, 0x20, + 0xcc, 0x7e, 0xf1, 0x04, 0xc3, 0xd9, 0x82, 0x4a, 0xae, 0xb6, 0xf0, 0xb5, 0x81, 0xee, 0x04, 0x56, + 0x70, 0x24, 0x92, 0x0d, 0xdd, 0x0f, 0x51, 0x30, 0x86, 0xa8, 0xfd, 0xfd, 0x22, 0xd4, 0x95, 0xeb, + 0xe1, 0x29, 0xd8, 0x82, 0xbb, 0x09, 0x5b, 0xf0, 0x84, 0x82, 0xc9, 0xb3, 0x2c, 0xc1, 0x6e, 0xca, + 0x12, 0x7c, 0x2b, 0xbf, 0xa8, 0xc7, 0xdb, 0x81, 0x7f, 0xab, 0x08, 0x33, 0x8a, 0x35, 0xaf, 0x85, + 0xf6, 0x2b, 0x70, 0x41, 0x04, 0x81, 0x6c, 0xea, 0x0f, 0x45, 0xde, 0x25, 0xde, 0x61, 0x65, 0x11, + 0xbf, 0xdd, 0x4e, 0x92, 0x30, 0xcd, 0xcb, 0x86, 0xb5, 0x28, 0xda, 0x61, 0x9b, 0x30, 0xe1, 0x36, + 0x16, 0xfb, 0x4d, 0x3e, 0xac, 0xdb, 0x29, 0x1a, 0x0e, 0x71, 0xa7, 0x4d, 0xc4, 0xe5, 0x73, 0x30, + 0x11, 0xff, 0xcb, 0x02, 0x4c, 0x45, 0xfd, 0x75, 0xee, 0x06, 0xe2, 0xbd, 0xa4, 0x81, 0x78, 0x39, + 0xf7, 0x70, 0x18, 0x61, 0x1e, 0xfe, 0x33, 0x35, 0x48, 0x9c, 0x69, 0x20, 0xbb, 0x30, 0x6f, 0x65, + 0x46, 0x66, 0xc6, 0x66, 0x9b, 0xf0, 0x90, 0xfe, 0xfa, 0x48, 0x4e, 0x7c, 0x0c, 0x0a, 0x19, 0x40, + 0xfd, 0x90, 0x7a, 0x81, 0x65, 0x50, 0xf5, 0x7c, 0xb7, 0x72, 0xab, 0x64, 0xd2, 0x08, 0x1e, 0xf6, + 0xe9, 0x7d, 0x29, 0x00, 0x43, 0x51, 0x64, 0x17, 0x2a, 0xd4, 0xec, 0x52, 0x95, 0x09, 0x2b, 0x67, + 0x66, 0xe2, 0xb0, 0x3f, 0xd9, 0x3f, 0x1f, 0x05, 0x34, 0xf1, 0xe3, 0x86, 0xa6, 0x72, 0x4e, 0x05, + 0xeb, 0x94, 0xe6, 0x25, 0x72, 0x10, 0x5a, 0x5b, 0x2b, 0x13, 0x9a, 0x3c, 0x1e, 0x63, 0x6b, 0xf5, + 0xa1, 0xf1, 0x40, 0x0f, 0xa8, 0xd7, 0xd3, 0xbd, 0x03, 0xb9, 0xdb, 0x18, 0xff, 0x09, 0xdf, 0x56, + 0x48, 0xd1, 0x13, 0x86, 0x45, 0x18, 0xc9, 0x21, 0x2e, 0x34, 0x02, 0xa9, 0x3e, 0x2b, 0x93, 0xf2, + 0xf8, 0x42, 0x95, 0x22, 0xee, 0xcb, 0xb3, 0x0d, 0xea, 0x2f, 0x46, 0x32, 0xc8, 0x61, 0x22, 0x8d, + 0xbd, 0xb8, 0xbc, 0xa0, 0x9d, 0xc3, 0x35, 0x21, 0xa1, 0xa2, 0xe5, 0x26, 0x3b, 0x1d, 0xbe, 0xf6, + 0x3f, 0x2a, 0xd1, 0xb4, 0xfc, 0xb4, 0xed, 0x84, 0x5f, 0x48, 0xda, 0x09, 0xaf, 0xa5, 0xed, 0x84, + 0x29, 0x9f, 0xff, 0xd9, 0xa3, 0xa1, 0x53, 0xe6, 0xb5, 0xf2, 0x39, 0x98, 0xd7, 0x5e, 0x81, 0xe6, + 0x21, 0x9f, 0x09, 0x44, 0x5a, 0xad, 0x0a, 0x5f, 0x46, 0xf8, 0xcc, 0x7e, 0x3f, 0x2a, 0xc6, 0x38, + 0x0f, 0xab, 0x22, 0x2f, 0xee, 0x09, 0x33, 0x59, 0xcb, 0x2a, 0x9d, 0xa8, 0x18, 0xe3, 0x3c, 0x3c, + 0x90, 0xd2, 0x72, 0x0e, 0x44, 0x85, 0x1a, 0xaf, 0x20, 0x02, 0x29, 0x55, 0x21, 0x46, 0x74, 0x72, + 0x03, 0xea, 0x03, 0x73, 0x4f, 0xf0, 0xd6, 0x39, 0x2f, 0xd7, 0x30, 0x77, 0x56, 0xd7, 0x64, 0x9a, + 0x2f, 0x45, 0x65, 0x2d, 0xe9, 0xe9, 0x7d, 0x45, 0xe0, 0x7b, 0x43, 0xd9, 0x92, 0xcd, 0xa8, 0x18, + 0xe3, 0x3c, 0xe4, 0x67, 0x61, 0xc6, 0xa3, 0xe6, 0xc0, 0xa0, 0x61, 0x2d, 0xe0, 0xb5, 0x64, 0xfe, + 0xd3, 0x38, 0x05, 0x53, 0x9c, 0x23, 0x8c, 0x84, 0xcd, 0xb1, 0x8c, 0x84, 0x5f, 0x85, 0x19, 0xd3, + 0xd3, 0x2d, 0x87, 0x9a, 0xf7, 0x1c, 0x1e, 0xd8, 0x21, 0xc3, 0x39, 0x43, 0x03, 0xfd, 0x6a, 0x82, + 0x8a, 0x29, 0x6e, 0xed, 0x1f, 0x17, 0xa1, 0x22, 0xb2, 0xb2, 0xae, 0xc3, 0x45, 0xcb, 0xb1, 0x02, + 0x4b, 0xb7, 0x57, 0xa9, 0xad, 0x1f, 0xc5, 0x03, 0x5c, 0x2a, 0xed, 0x17, 0xd9, 0x46, 0x7b, 0x7d, + 0x98, 0x8c, 0x59, 0x75, 0x58, 0xe7, 0x04, 0x62, 0xf9, 0x56, 0x28, 0xc2, 0x8e, 0x26, 0x52, 0x82, + 0x27, 0x28, 0x98, 0xe2, 0x64, 0xca, 0x50, 0x7f, 0x28, 0x72, 0xa5, 0x22, 0x94, 0xa1, 0x64, 0x30, + 0x49, 0x92, 0x8f, 0x2b, 0xe9, 0x03, 0xae, 0x10, 0x87, 0x87, 0xa6, 0x64, 0x10, 0x9c, 0x50, 0xd2, + 0x53, 0x34, 0x1c, 0xe2, 0x66, 0x08, 0x7b, 0xba, 0x65, 0x0f, 0x3c, 0x1a, 0x21, 0x54, 0x22, 0x84, + 0xb5, 0x14, 0x0d, 0x87, 0xb8, 0xb5, 0x6d, 0x80, 0xad, 0x81, 0xed, 0xeb, 0x3c, 0x03, 0xcf, 0xc4, + 0xee, 0x85, 0xf8, 0xfd, 0x22, 0x4c, 0x09, 0x58, 0xb9, 0x91, 0x5e, 0x02, 0x90, 0x89, 0x7e, 0x4c, + 0xd3, 0x93, 0xba, 0x41, 0x34, 0xc1, 0x85, 0x14, 0x8c, 0x71, 0x9d, 0x2e, 0xa4, 0xec, 0x75, 0x98, + 0x52, 0x21, 0x62, 0x5c, 0xed, 0x48, 0x85, 0xd7, 0xae, 0xc4, 0x68, 0x98, 0xe0, 0x24, 0xab, 0xac, + 0xf7, 0x77, 0xc5, 0xc1, 0x72, 0xcb, 0x75, 0x78, 0x6d, 0x91, 0x81, 0x21, 0x3c, 0x5a, 0xd9, 0x49, + 0xd1, 0x71, 0xa8, 0x06, 0xf9, 0x3c, 0xd4, 0x7b, 0xfa, 0xc3, 0x1d, 0x47, 0x37, 0x0e, 0xe4, 0x14, + 0x12, 0xea, 0x15, 0x9b, 0xb2, 0x1c, 0x43, 0x0e, 0xa2, 0xcb, 0x7d, 0x78, 0x35, 0xef, 0xe1, 0xc3, + 0xf0, 0x95, 0x0d, 0xed, 0xc4, 0xff, 0x6b, 0x01, 0xc8, 0xf0, 0xb9, 0x1e, 0xb2, 0x0f, 0x55, 0x87, + 0x1b, 0x97, 0x73, 0x5f, 0x2d, 0x11, 0xb3, 0x51, 0x8b, 0x55, 0x5f, 0x16, 0x48, 0x7c, 0xe2, 0x40, + 0x9d, 0x3e, 0x0c, 0xa8, 0xe7, 0x84, 0xe7, 0xfc, 0x26, 0x73, 0x8d, 0x85, 0xd8, 0x6c, 0x4b, 0x64, + 0x0c, 0x65, 0x68, 0xbf, 0x57, 0x84, 0x66, 0x8c, 0xef, 0x49, 0x36, 0x1b, 0x9e, 0x6a, 0x44, 0xd8, + 0x74, 0x77, 0x3c, 0x5b, 0x8e, 0xad, 0x58, 0xaa, 0x11, 0x49, 0xc2, 0x0d, 0x8c, 0xf3, 0xb1, 0x01, + 0xdc, 0xd3, 0xfd, 0x20, 0x31, 0xca, 0xc2, 0x01, 0xbc, 0x19, 0x52, 0x30, 0xc6, 0x45, 0xae, 0xcb, + 0x8b, 0x48, 0xca, 0xc9, 0x84, 0xac, 0x23, 0x6e, 0x19, 0xa9, 0x4c, 0xe0, 0x96, 0x11, 0xd2, 0x85, + 0x59, 0xd5, 0x6a, 0x45, 0x3d, 0x5b, 0xba, 0x4e, 0x31, 0xf3, 0xa4, 0x20, 0x70, 0x08, 0x54, 0xfb, + 0x5e, 0x01, 0xa6, 0x13, 0x16, 0x45, 0x91, 0x4a, 0x55, 0x9d, 0x4a, 0x4b, 0xa4, 0x52, 0x8d, 0x1d, + 0x26, 0x7b, 0x19, 0xaa, 0xa2, 0x83, 0xd2, 0xc1, 0xe6, 0xa2, 0x0b, 0x51, 0x52, 0x99, 0xaa, 0x20, + 0x7d, 0x16, 0x69, 0x55, 0x41, 0x3a, 0x35, 0x50, 0xd1, 0x85, 0x2b, 0x50, 0xb4, 0x4e, 0xf6, 0x74, + 0xcc, 0x15, 0x28, 0xca, 0x31, 0xe4, 0xd0, 0xfe, 0x36, 0x6f, 0x77, 0xe0, 0x1d, 0x85, 0xa6, 0x92, + 0x2e, 0xd4, 0x64, 0x80, 0xb1, 0xfc, 0x34, 0xde, 0xcc, 0x61, 0xe6, 0xe4, 0x38, 0x32, 0x44, 0x56, + 0x37, 0x0e, 0xee, 0xed, 0xed, 0xa1, 0x42, 0x27, 0x37, 0xa1, 0xe1, 0x3a, 0x72, 0x4a, 0x96, 0x8f, + 0xff, 0x39, 0xa6, 0x0a, 0xdc, 0x53, 0x85, 0x8f, 0x8e, 0x5b, 0x97, 0xc3, 0x3f, 0x89, 0x46, 0x62, + 0x54, 0x53, 0xfb, 0x13, 0x05, 0x78, 0x01, 0x5d, 0xdb, 0xb6, 0x9c, 0x6e, 0xd2, 0x95, 0x4d, 0x6c, + 0x98, 0x11, 0x33, 0xcd, 0xa1, 0x6e, 0xd9, 0xfa, 0xae, 0x4d, 0x9f, 0x68, 0xea, 0x18, 0x04, 0x96, + 0xbd, 0x20, 0x2e, 0x66, 0x5d, 0x58, 0x77, 0x82, 0x7b, 0x5e, 0x27, 0xf0, 0x2c, 0xa7, 0x2b, 0x96, + 0xbd, 0xcd, 0x04, 0x16, 0xa6, 0xb0, 0xb5, 0x7f, 0x53, 0x06, 0x1e, 0xbc, 0x4a, 0xbe, 0x08, 0x8d, + 0x1e, 0x35, 0xf6, 0x75, 0xc7, 0xf2, 0x55, 0x52, 0xea, 0x2b, 0xec, 0xb9, 0x36, 0x55, 0xe1, 0x23, + 0xf6, 0x2a, 0x96, 0x3b, 0x1b, 0xfc, 0x1c, 0x59, 0xc4, 0x4b, 0x0c, 0xa8, 0x76, 0x7d, 0x5f, 0xef, + 0x5b, 0xb9, 0x63, 0x86, 0x44, 0x12, 0x60, 0x31, 0x1d, 0x89, 0xdf, 0x28, 0xa1, 0x89, 0x01, 0x95, + 0xbe, 0xad, 0x5b, 0x4e, 0xee, 0x8b, 0x04, 0xd9, 0x13, 0x6c, 0x31, 0x24, 0xb1, 0xde, 0xf1, 0x9f, + 0x28, 0xb0, 0xc9, 0x00, 0x9a, 0xbe, 0xe1, 0xe9, 0x3d, 0x7f, 0x5f, 0x5f, 0x7a, 0xf5, 0xb5, 0xdc, + 0xbb, 0xb9, 0x48, 0x94, 0x50, 0x2e, 0x57, 0x70, 0x79, 0xb3, 0x73, 0x7b, 0x79, 0xe9, 0xd5, 0xd7, + 0x30, 0x2e, 0x27, 0x2e, 0xf6, 0xd5, 0x57, 0x96, 0xe4, 0x0c, 0x32, 0x71, 0xb1, 0xaf, 0xbe, 0xb2, + 0x84, 0x71, 0x39, 0xac, 0x4b, 0xdd, 0xd8, 0x32, 0x96, 0x4f, 0xe0, 0xbd, 0xc8, 0x2d, 0xc0, 0x7f, + 0xa2, 0xc0, 0xd6, 0xfe, 0x67, 0x01, 0x1a, 0x21, 0x9d, 0x4d, 0x94, 0x22, 0xbd, 0xe1, 0xfa, 0xea, + 0xd9, 0x74, 0x13, 0x3e, 0x51, 0xae, 0xc8, 0xaa, 0x18, 0x82, 0x90, 0x77, 0x61, 0x4a, 0xfc, 0x96, + 0xe9, 0x86, 0x8b, 0x67, 0xce, 0x69, 0xbc, 0x12, 0xab, 0x8e, 0x09, 0x30, 0xf2, 0x65, 0x98, 0xe6, + 0x7a, 0xd0, 0x4d, 0xc7, 0xec, 0xbb, 0x96, 0xbc, 0x1d, 0x28, 0x96, 0xd9, 0x69, 0x3b, 0x4e, 0xc4, + 0x24, 0x6f, 0xf8, 0xe0, 0xfc, 0x4d, 0x90, 0x1d, 0x00, 0xb6, 0x52, 0xc8, 0x56, 0x9e, 0xe9, 0xd1, + 0xb9, 0x71, 0x74, 0x27, 0xac, 0x8c, 0x31, 0xa0, 0x8c, 0xac, 0xd1, 0xc5, 0x49, 0x67, 0x8d, 0x5e, + 0x84, 0xc6, 0xbe, 0xee, 0x98, 0xfe, 0xbe, 0x7e, 0x40, 0xe5, 0x89, 0x8a, 0x70, 0xe7, 0x7e, 0x5b, + 0x11, 0x30, 0xe2, 0xd1, 0xfe, 0x6e, 0x15, 0x44, 0x18, 0x15, 0x9b, 0xd2, 0x4d, 0xcb, 0x17, 0xe7, + 0x9e, 0x0a, 0xbc, 0x66, 0x38, 0xa5, 0xaf, 0xca, 0x72, 0x0c, 0x39, 0xc8, 0x15, 0x28, 0xf5, 0x2c, + 0x47, 0x2a, 0xec, 0xdc, 0xef, 0xb1, 0x69, 0x39, 0xc8, 0xca, 0x38, 0x49, 0x7f, 0x28, 0x15, 0x72, + 0x41, 0xd2, 0x1f, 0x22, 0x2b, 0x23, 0x5f, 0x81, 0x0b, 0xb6, 0xeb, 0x1e, 0xb0, 0xc9, 0x39, 0x1e, + 0x19, 0x3e, 0x2d, 0x2c, 0x91, 0x1b, 0x49, 0x12, 0xa6, 0x79, 0xc9, 0x0e, 0xbc, 0xf8, 0x01, 0xf5, + 0x5c, 0xb9, 0x1a, 0x75, 0x6c, 0x4a, 0xfb, 0x0a, 0x46, 0xa8, 0x81, 0x3c, 0x70, 0xfd, 0xe7, 0xb3, + 0x59, 0x70, 0x54, 0x5d, 0x7e, 0xd4, 0x46, 0xf7, 0xba, 0x34, 0xd8, 0xf2, 0x5c, 0xa6, 0xea, 0x5b, + 0x4e, 0x57, 0xc1, 0x56, 0x23, 0xd8, 0xed, 0x6c, 0x16, 0x1c, 0x55, 0x97, 0xbc, 0x03, 0x73, 0x82, + 0x24, 0x94, 0xc2, 0x65, 0x31, 0x89, 0x5b, 0xb6, 0xba, 0xdd, 0x78, 0x5a, 0xb8, 0x97, 0xb7, 0x47, + 0xf0, 0xe0, 0xc8, 0xda, 0xe4, 0x2d, 0x98, 0x55, 0xc1, 0x05, 0x5b, 0xd4, 0xeb, 0x84, 0xa1, 0x75, + 0xd3, 0xea, 0x84, 0x81, 0x8a, 0xb0, 0xc7, 0x14, 0x17, 0x0e, 0xd5, 0x23, 0x08, 0x97, 0x79, 0xfc, + 0xdc, 0x4e, 0x7f, 0xc5, 0x75, 0x6d, 0xd3, 0x7d, 0xe0, 0xa8, 0x67, 0x17, 0xfb, 0x5b, 0x1e, 0x4f, + 0xd0, 0xc9, 0xe4, 0xc0, 0x11, 0x35, 0xd9, 0x93, 0x73, 0xca, 0xaa, 0xfb, 0xc0, 0x49, 0xa3, 0x42, + 0xf4, 0xe4, 0x9d, 0x11, 0x3c, 0x38, 0xb2, 0x36, 0x59, 0x03, 0x92, 0x7e, 0x82, 0x9d, 0xbe, 0x8c, + 0x78, 0xb9, 0x2c, 0xf2, 0x9b, 0xa5, 0xa9, 0x98, 0x51, 0x83, 0x6c, 0xc0, 0xa5, 0x74, 0x29, 0x13, + 0x27, 0x83, 0x5f, 0x78, 0x66, 0x73, 0xcc, 0xa0, 0x63, 0x66, 0x2d, 0xed, 0xef, 0x15, 0x61, 0x3a, + 0x91, 0x10, 0xe7, 0x99, 0x4b, 0x3c, 0xc2, 0xf6, 0xda, 0x3d, 0xbf, 0xbb, 0xbe, 0x7a, 0x9b, 0xea, + 0x26, 0xf5, 0xd4, 0x79, 0xab, 0x86, 0x54, 0x3a, 0x12, 0x14, 0x4c, 0x71, 0x92, 0x3d, 0xa8, 0x08, + 0xb7, 0x5a, 0xde, 0xcb, 0xd1, 0x54, 0x1f, 0x71, 0xdf, 0x9a, 0xbc, 0x51, 0xd0, 0xf5, 0x28, 0x0a, + 0x78, 0x2d, 0x80, 0xa9, 0x38, 0x07, 0x9b, 0x48, 0xa2, 0x4d, 0x45, 0x2d, 0xb1, 0xa1, 0x58, 0x87, + 0x52, 0x10, 0x8c, 0x9b, 0xd2, 0x44, 0xb8, 0x69, 0xb7, 0x37, 0x90, 0x61, 0x68, 0x7b, 0xec, 0xdd, + 0xf9, 0xbe, 0xe5, 0x3a, 0xf2, 0x7e, 0x8b, 0x1d, 0xa8, 0x49, 0x63, 0xc3, 0x98, 0x29, 0x59, 0xb8, + 0x26, 0xaa, 0xbc, 0x14, 0x0a, 0x4b, 0xfb, 0x57, 0x45, 0x68, 0x84, 0x56, 0xc5, 0x53, 0xdc, 0x1b, + 0xe1, 0x42, 0x23, 0x8c, 0xff, 0xcd, 0x7d, 0xf3, 0x73, 0x14, 0x96, 0xca, 0x0d, 0x61, 0xe1, 0x5f, + 0x8c, 0x64, 0xc4, 0x63, 0x8b, 0x4b, 0x39, 0x62, 0x8b, 0xfb, 0x50, 0x0b, 0x3c, 0xab, 0xdb, 0x95, + 0x7b, 0xb0, 0x3c, 0xc1, 0xc5, 0x61, 0x77, 0x6d, 0x0b, 0x40, 0xd9, 0xb3, 0xe2, 0x0f, 0x2a, 0x31, + 0xda, 0xfb, 0x30, 0x9b, 0xe6, 0xe4, 0x1b, 0x14, 0x63, 0x9f, 0x9a, 0x03, 0x5b, 0xf5, 0x71, 0xb4, + 0x41, 0x91, 0xe5, 0x18, 0x72, 0x90, 0x1b, 0x50, 0x67, 0xaf, 0xe9, 0x03, 0xd7, 0x51, 0x9b, 0x04, + 0xae, 0xc2, 0x6c, 0xcb, 0x32, 0x0c, 0xa9, 0xda, 0x7f, 0x2a, 0xc1, 0x95, 0xc8, 0x36, 0xbc, 0xa9, + 0x3b, 0x7a, 0xf7, 0x14, 0xd7, 0xfd, 0x7e, 0x72, 0xc8, 0xf5, 0xac, 0x97, 0xff, 0x94, 0x9e, 0x81, + 0xcb, 0x7f, 0xfe, 0x6f, 0x11, 0xf8, 0x59, 0x05, 0xf2, 0x2d, 0x98, 0xd2, 0x63, 0x37, 0xbd, 0xcb, + 0xd7, 0x79, 0x33, 0xf7, 0xeb, 0xe4, 0x47, 0x22, 0x42, 0xb3, 0x59, 0xbc, 0x14, 0x13, 0x02, 0x89, + 0x0b, 0xf5, 0x3d, 0xdd, 0xb6, 0x99, 0x2e, 0x94, 0xdb, 0xd7, 0x9d, 0x10, 0xce, 0x87, 0xf9, 0x9a, + 0x84, 0xc6, 0x50, 0x08, 0xf9, 0x4e, 0x01, 0xa6, 0xbd, 0xf8, 0x66, 0x58, 0xbe, 0x90, 0x3c, 0x91, + 0x50, 0x31, 0xb4, 0x78, 0x74, 0x6a, 0x7c, 0xc7, 0x9d, 0x94, 0xa9, 0xfd, 0x87, 0x02, 0x4c, 0x77, + 0x6c, 0xcb, 0xb4, 0x9c, 0xee, 0x39, 0xde, 0x3d, 0x74, 0x0f, 0x2a, 0xbe, 0x6d, 0x99, 0x74, 0xcc, + 0xd5, 0x44, 0xac, 0x63, 0x0c, 0x00, 0x05, 0x4e, 0xf2, 0x32, 0xa3, 0xd2, 0x29, 0x2e, 0x33, 0xfa, + 0x8f, 0x35, 0x90, 0xa7, 0x6e, 0xc8, 0x00, 0x1a, 0x5d, 0x75, 0x47, 0x8a, 0x7c, 0xc6, 0xdb, 0x39, + 0xf2, 0xeb, 0x26, 0x6e, 0x5b, 0x11, 0x73, 0x7f, 0x58, 0x88, 0x91, 0x24, 0x42, 0xa1, 0xc2, 0xcf, + 0xb6, 0xe6, 0x36, 0x1e, 0xc6, 0x4e, 0x31, 0x8b, 0x9e, 0xe1, 0x05, 0x28, 0xd0, 0x89, 0x0e, 0xe5, + 0xfd, 0x20, 0xe8, 0xcb, 0xc1, 0x34, 0xbe, 0x29, 0x36, 0x4a, 0xf1, 0x26, 0x74, 0x22, 0xf6, 0x1f, + 0x39, 0x34, 0x13, 0xe1, 0xe8, 0xe1, 0x45, 0xae, 0x2b, 0xb9, 0xa2, 0xae, 0xe2, 0x22, 0xd8, 0x7f, + 0xe4, 0xd0, 0xe4, 0x17, 0xa1, 0x19, 0x78, 0xba, 0xe3, 0xef, 0xb9, 0x5e, 0x8f, 0x7a, 0xd2, 0x02, + 0xb0, 0x96, 0xe3, 0x96, 0xfd, 0xed, 0x08, 0x4d, 0x78, 0x30, 0x12, 0x45, 0x18, 0x97, 0x46, 0x0e, + 0xa0, 0x3e, 0x30, 0x45, 0xc3, 0xa4, 0x29, 0x60, 0x39, 0x87, 0xe4, 0x78, 0x4c, 0x95, 0xfa, 0x87, + 0xa1, 0x80, 0xe4, 0x9d, 0xc5, 0xb5, 0x49, 0xdd, 0x59, 0x1c, 0x1f, 0x8d, 0x59, 0xf9, 0xa7, 0x48, + 0x4f, 0xea, 0xb5, 0x4e, 0x57, 0x86, 0x84, 0xae, 0xe5, 0x56, 0x39, 0x85, 0xc8, 0x66, 0xa8, 0x1b, + 0x3b, 0x5d, 0x54, 0x32, 0x88, 0x05, 0xd5, 0x3e, 0xb7, 0xed, 0xe7, 0xbe, 0xbf, 0x3e, 0xee, 0x7e, + 0x11, 0x73, 0x8d, 0x28, 0x41, 0x29, 0x40, 0xeb, 0x81, 0xf4, 0xea, 0x12, 0x23, 0x71, 0x25, 0x9c, + 0x38, 0xb3, 0xbc, 0x78, 0xba, 0xa9, 0x27, 0xbc, 0x9b, 0x2c, 0x76, 0x25, 0x45, 0xe6, 0xdd, 0x6f, + 0xda, 0xbf, 0x2e, 0x42, 0x69, 0x7b, 0xa3, 0x23, 0xd2, 0x4c, 0xf3, 0x4b, 0x26, 0x69, 0xe7, 0xc0, + 0xea, 0xdf, 0xa7, 0x9e, 0xb5, 0x77, 0x24, 0x77, 0xf9, 0xb1, 0x34, 0xd3, 0x69, 0x0e, 0xcc, 0xa8, + 0xc5, 0x8d, 0x38, 0xfa, 0x0a, 0xf5, 0x72, 0x18, 0x71, 0x96, 0xa3, 0xea, 0x98, 0x00, 0x23, 0x3b, + 0x00, 0x46, 0x04, 0x5d, 0x3a, 0xb3, 0xe5, 0x25, 0x06, 0x1c, 0x03, 0x22, 0x08, 0x8d, 0x03, 0xc6, + 0xca, 0x51, 0xcb, 0x67, 0x41, 0xe5, 0x83, 0xf4, 0x8e, 0xaa, 0x8b, 0x11, 0x8c, 0xe6, 0xc0, 0x74, + 0xe2, 0x9e, 0x38, 0xf2, 0x25, 0xa8, 0xbb, 0xfd, 0xd8, 0xcc, 0xdd, 0xe0, 0x71, 0xee, 0xf5, 0x7b, + 0xb2, 0xec, 0xd1, 0x71, 0x6b, 0x7a, 0xc3, 0xed, 0x5a, 0x86, 0x2a, 0xc0, 0x90, 0x9d, 0x68, 0x50, + 0xe5, 0x27, 0xaa, 0xd5, 0x2d, 0x71, 0x7c, 0xe8, 0xf0, 0x8b, 0x9c, 0x7c, 0x94, 0x14, 0xed, 0x97, + 0xcb, 0x10, 0xc5, 0x42, 0x10, 0x1f, 0xaa, 0xe2, 0x34, 0x97, 0x5c, 0x24, 0xce, 0xf5, 0xe0, 0x98, + 0x14, 0x45, 0xba, 0x50, 0x7a, 0xdf, 0xdd, 0xcd, 0xbd, 0x46, 0xc4, 0xd2, 0xc2, 0x08, 0xa3, 0x67, + 0xac, 0x00, 0x99, 0x04, 0xf2, 0x17, 0x0b, 0xf0, 0xbc, 0x9f, 0xd6, 0xb2, 0xe5, 0x70, 0xc0, 0xfc, + 0xdb, 0x89, 0xb4, 0xde, 0x2e, 0x0f, 0x24, 0x8c, 0x22, 0xe3, 0x70, 0x5b, 0x58, 0xff, 0x8b, 0x20, + 0x05, 0x39, 0x9c, 0x6e, 0xe5, 0xbc, 0x0d, 0x3b, 0xd9, 0xff, 0xc9, 0x32, 0x94, 0xa2, 0xb4, 0x6f, + 0x17, 0xa1, 0x19, 0x5b, 0x18, 0x72, 0x5f, 0x3e, 0xf8, 0x30, 0x75, 0xf9, 0xe0, 0xd6, 0xf8, 0x31, + 0x3b, 0x51, 0xab, 0xce, 0xfb, 0xfe, 0xc1, 0x7f, 0x58, 0x84, 0xd2, 0xce, 0xea, 0x5a, 0x72, 0x7f, + 0x5c, 0x78, 0x0a, 0xfb, 0xe3, 0x7d, 0xa8, 0xed, 0x0e, 0x2c, 0x3b, 0xb0, 0x9c, 0xdc, 0x89, 0xab, + 0xd4, 0x5d, 0x8d, 0xd2, 0x69, 0x25, 0x50, 0x51, 0xc1, 0x93, 0x2e, 0xd4, 0xba, 0x22, 0x73, 0x70, + 0xee, 0x48, 0x66, 0x99, 0x81, 0x58, 0x08, 0x92, 0x7f, 0x50, 0xa1, 0x6b, 0x47, 0x50, 0xdd, 0x59, + 0x95, 0x3b, 0x8c, 0xa7, 0xdb, 0x9b, 0xda, 0x2f, 0x42, 0xa8, 0x70, 0x3c, 0x7d, 0xe1, 0xff, 0xa5, + 0x00, 0x49, 0x1d, 0xeb, 0xe9, 0x8f, 0xa6, 0x83, 0xf4, 0x68, 0x5a, 0x9d, 0xc4, 0xc7, 0x97, 0x3d, + 0xa0, 0xb4, 0x7f, 0x51, 0x80, 0xd4, 0x11, 0x5c, 0xf2, 0x9a, 0x4c, 0x42, 0x99, 0x0c, 0x19, 0x55, + 0x49, 0x28, 0x49, 0x92, 0x3b, 0x96, 0x8c, 0xf2, 0x43, 0xb6, 0x33, 0x8c, 0x7b, 0x42, 0x65, 0xf3, + 0xef, 0x8e, 0xbf, 0x33, 0xcc, 0xf2, 0xab, 0xca, 0xb0, 0xe6, 0x38, 0x09, 0x93, 0x72, 0xb5, 0xbf, + 0x53, 0x84, 0xea, 0x53, 0xcb, 0x3a, 0x42, 0x13, 0x91, 0xe6, 0x2b, 0x39, 0x67, 0xfb, 0x91, 0x71, + 0xe6, 0xbd, 0x54, 0x9c, 0xf9, 0xcd, 0xbc, 0x82, 0x1e, 0x1f, 0x65, 0xfe, 0xcf, 0x0a, 0x20, 0xd7, + 0x9a, 0x75, 0xc7, 0x0f, 0x74, 0xc7, 0xa0, 0xc4, 0x08, 0x17, 0xb6, 0xbc, 0xe1, 0x8c, 0x32, 0xe4, + 0x57, 0xe8, 0x32, 0xfc, 0xb7, 0x5a, 0xc8, 0xc8, 0xe7, 0xa1, 0xbe, 0xef, 0xfa, 0x01, 0x5f, 0xbc, + 0x8a, 0x49, 0xeb, 0xdc, 0x6d, 0x59, 0x8e, 0x21, 0x47, 0x3a, 0x2e, 0xa1, 0x32, 0x3a, 0x2e, 0x41, + 0xfb, 0xcd, 0x22, 0x4c, 0x7d, 0x5c, 0xd2, 0x9a, 0x64, 0xc5, 0xe5, 0x97, 0x72, 0xc6, 0xe5, 0x97, + 0xcf, 0x12, 0x97, 0xaf, 0xfd, 0xa0, 0x00, 0xf0, 0xd4, 0x72, 0xaa, 0x98, 0xc9, 0x90, 0xf9, 0xdc, + 0xe3, 0x2a, 0x3b, 0x60, 0xfe, 0x6f, 0x56, 0xd4, 0x23, 0xf1, 0x70, 0xf9, 0x0f, 0x0b, 0x30, 0xa3, + 0x27, 0x42, 0xd0, 0x73, 0xeb, 0xcb, 0xa9, 0x88, 0xf6, 0x30, 0x82, 0x32, 0x59, 0x8e, 0x29, 0xb1, + 0xe4, 0xf5, 0xe8, 0xfe, 0x83, 0xbb, 0xd1, 0xb0, 0x1f, 0xba, 0xb8, 0x40, 0xc4, 0xcc, 0xc5, 0x39, + 0x9f, 0x10, 0xf2, 0x5f, 0x9a, 0x48, 0xc8, 0x7f, 0xfc, 0x30, 0x73, 0xf9, 0xb1, 0x87, 0x99, 0x0f, + 0xa1, 0xb1, 0xe7, 0xb9, 0x3d, 0x1e, 0x55, 0x3f, 0x57, 0xe1, 0xaf, 0xf2, 0x66, 0x8e, 0x85, 0xb2, + 0xb7, 0x6b, 0x39, 0xd4, 0xe4, 0x11, 0xfb, 0xa1, 0x8d, 0x6c, 0x4d, 0xe1, 0x63, 0x24, 0x8a, 0xbb, + 0x15, 0x5c, 0x21, 0xb5, 0x3a, 0x49, 0xa9, 0xe1, 0x5c, 0xb2, 0x2d, 0xd0, 0x51, 0x89, 0x49, 0x46, + 0xd2, 0xd7, 0x9e, 0x4e, 0x24, 0xbd, 0xf6, 0xa7, 0x6b, 0x6a, 0x02, 0x7b, 0xe6, 0x52, 0x6d, 0x7f, + 0x92, 0x82, 0xa2, 0x4b, 0x87, 0xf2, 0x43, 0xd4, 0x9f, 0x62, 0x7e, 0x88, 0xc6, 0x64, 0xf2, 0x43, + 0x40, 0xbe, 0xfc, 0x10, 0xcd, 0x09, 0xe5, 0x87, 0x98, 0x9a, 0x54, 0x7e, 0x88, 0xe9, 0xb1, 0xf2, + 0x43, 0xcc, 0x9c, 0x2a, 0x3f, 0xc4, 0x71, 0x09, 0x52, 0x9b, 0xf1, 0x4f, 0x7c, 0x7c, 0x3f, 0x56, + 0x3e, 0xbe, 0xef, 0x16, 0x21, 0x9a, 0x88, 0xcf, 0x18, 0x03, 0xf5, 0x0e, 0x0f, 0x43, 0xe7, 0x47, + 0x1a, 0xf2, 0xdc, 0x50, 0xbf, 0x29, 0x31, 0x30, 0x44, 0x23, 0x3e, 0x80, 0x15, 0xde, 0x12, 0x93, + 0xdb, 0x5b, 0x12, 0x5d, 0x38, 0x23, 0x8c, 0xa4, 0xd1, 0x7f, 0x8c, 0x89, 0xd1, 0xfe, 0x69, 0x11, + 0xe4, 0x75, 0x42, 0x84, 0x42, 0x65, 0xcf, 0x7a, 0x48, 0xcd, 0xdc, 0x71, 0xeb, 0x6b, 0x0c, 0x45, + 0xde, 0x59, 0xc4, 0xdd, 0x41, 0xbc, 0x00, 0x05, 0x3a, 0xb7, 0xf3, 0x0b, 0xf7, 0x9e, 0xec, 0xbf, + 0x1c, 0x76, 0xfe, 0xb8, 0x9b, 0x50, 0xda, 0xf9, 0x45, 0x11, 0x2a, 0x19, 0xc2, 0xad, 0xc0, 0x23, + 0x3d, 0x72, 0x7b, 0x33, 0x13, 0x11, 0x23, 0xca, 0xad, 0xe0, 0x8b, 0x04, 0x31, 0x52, 0x46, 0xfb, + 0x17, 0xbe, 0xff, 0xa3, 0x6b, 0xcf, 0xfd, 0xe0, 0x47, 0xd7, 0x9e, 0xfb, 0xe1, 0x8f, 0xae, 0x3d, + 0xf7, 0xcb, 0x27, 0xd7, 0x0a, 0xdf, 0x3f, 0xb9, 0x56, 0xf8, 0xc1, 0xc9, 0xb5, 0xc2, 0x0f, 0x4f, + 0xae, 0x15, 0xfe, 0xdd, 0xc9, 0xb5, 0xc2, 0x9f, 0xfb, 0xf7, 0xd7, 0x9e, 0xfb, 0xf9, 0x2f, 0x46, + 0x4d, 0x58, 0x54, 0x4d, 0x58, 0x54, 0x02, 0x17, 0xfb, 0x07, 0xdd, 0x45, 0xd6, 0x84, 0xa8, 0x44, + 0x35, 0xe1, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0x83, 0x43, 0xf5, 0x85, 0xa3, 0xa3, 0x00, 0x00, } func (m *AbstractPodTemplate) Marshal() (dAtA []byte, err error) { @@ -8034,6 +8067,18 @@ func (m *SASL) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.OAuth != nil { + { + size, err := m.OAuth.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } if m.SCRAMSHA512 != nil { { size, err := m.SCRAMSHA512.MarshalToSizedBuffer(dAtA[:i]) @@ -8092,6 +8137,58 @@ func (m *SASL) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *SASLOAuth) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SASLOAuth) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *SASLOAuth) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.TokenEndpoint) + copy(dAtA[i:], m.TokenEndpoint) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.TokenEndpoint))) + i-- + dAtA[i] = 0x1a + if m.ClientSecret != nil { + { + size, err := m.ClientSecret.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.ClientID != nil { + { + size, err := m.ClientID.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *SASLPlain) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -11519,6 +11616,29 @@ func (m *SASL) Size() (n int) { l = m.SCRAMSHA512.Size() n += 1 + l + sovGenerated(uint64(l)) } + if m.OAuth != nil { + l = m.OAuth.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *SASLOAuth) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ClientID != nil { + l = m.ClientID.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.ClientSecret != nil { + l = m.ClientSecret.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + l = len(m.TokenEndpoint) + n += 1 + l + sovGenerated(uint64(l)) return n } @@ -13328,6 +13448,19 @@ func (this *SASL) String() string { `Plain:` + strings.Replace(this.Plain.String(), "SASLPlain", "SASLPlain", 1) + `,`, `SCRAMSHA256:` + strings.Replace(this.SCRAMSHA256.String(), "SASLPlain", "SASLPlain", 1) + `,`, `SCRAMSHA512:` + strings.Replace(this.SCRAMSHA512.String(), "SASLPlain", "SASLPlain", 1) + `,`, + `OAuth:` + strings.Replace(this.OAuth.String(), "SASLOAuth", "SASLOAuth", 1) + `,`, + `}`, + }, "") + return s +} +func (this *SASLOAuth) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&SASLOAuth{`, + `ClientID:` + strings.Replace(fmt.Sprintf("%v", this.ClientID), "SecretKeySelector", "v1.SecretKeySelector", 1) + `,`, + `ClientSecret:` + strings.Replace(fmt.Sprintf("%v", this.ClientSecret), "SecretKeySelector", "v1.SecretKeySelector", 1) + `,`, + `TokenEndpoint:` + fmt.Sprintf("%v", this.TokenEndpoint) + `,`, `}`, }, "") return s @@ -28675,6 +28808,196 @@ func (m *SASL) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field OAuth", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.OAuth == nil { + m.OAuth = &SASLOAuth{} + } + if err := m.OAuth.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SASLOAuth) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SASLOAuth: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SASLOAuth: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientID", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ClientID == nil { + m.ClientID = &v1.SecretKeySelector{} + } + if err := m.ClientID.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientSecret", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ClientSecret == nil { + m.ClientSecret = &v1.SecretKeySelector{} + } + if err := m.ClientSecret.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TokenEndpoint", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TokenEndpoint = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/pkg/apis/numaflow/v1alpha1/generated.proto b/pkg/apis/numaflow/v1alpha1/generated.proto index 1a677e81a..4c488d0ae 100644 --- a/pkg/apis/numaflow/v1alpha1/generated.proto +++ b/pkg/apis/numaflow/v1alpha1/generated.proto @@ -1442,6 +1442,21 @@ message SASL { // SASLSCRAMSHA512 contains the sasl plain config // +optional optional SASLPlain scramsha512 = 5; + + // OAuth contains the oauth config + // +optional + optional SASLOAuth oauth = 6; +} + +message SASLOAuth { + // ClientID refers to the secret that contains the client id + optional .k8s.io.api.core.v1.SecretKeySelector clientID = 1; + + // ClientSecret refers to the secret that contains the client secret + optional .k8s.io.api.core.v1.SecretKeySelector clientSecret = 2; + + // TokenEndpoint refers to the token endpoint + optional string tokenEndpoint = 3; } message SASLPlain { diff --git a/pkg/apis/numaflow/v1alpha1/sasl.go b/pkg/apis/numaflow/v1alpha1/sasl.go index 813d2303f..108791fe2 100644 --- a/pkg/apis/numaflow/v1alpha1/sasl.go +++ b/pkg/apis/numaflow/v1alpha1/sasl.go @@ -33,6 +33,9 @@ type SASL struct { // SASLSCRAMSHA512 contains the sasl plain config // +optional SCRAMSHA512 *SASLPlain `json:"scramsha512" protobuf:"bytes,5,opt,name=scramsha512"` + // OAuth contains the oauth config + // +optional + OAuth *SASLOAuth `json:"oauth" protobuf:"bytes,6,opt,name=oauth"` } // SASLType describes the SASL type @@ -96,3 +99,12 @@ type SASLPlain struct { PasswordSecret *corev1.SecretKeySelector `json:"passwordSecret" protobuf:"bytes,2,opt,name=passwordSecret"` Handshake bool `json:"handshake" protobuf:"bytes,3,opt,name=handshake"` } + +type SASLOAuth struct { + // ClientID refers to the secret that contains the client id + ClientID *corev1.SecretKeySelector `json:"clientID" protobuf:"bytes,1,opt,name=clientID"` + // ClientSecret refers to the secret that contains the client secret + ClientSecret *corev1.SecretKeySelector `json:"clientSecret" protobuf:"bytes,2,opt,name=clientSecret"` + // TokenEndpoint refers to the token endpoint + TokenEndpoint string `json:"tokenEndpoint" protobuf:"bytes,3,opt,name=tokenEndpoint"` +} diff --git a/pkg/apis/numaflow/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/numaflow/v1alpha1/zz_generated.deepcopy.go index ee979ab2b..ed337f37b 100644 --- a/pkg/apis/numaflow/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/numaflow/v1alpha1/zz_generated.deepcopy.go @@ -2191,6 +2191,11 @@ func (in *SASL) DeepCopyInto(out *SASL) { *out = new(SASLPlain) (*in).DeepCopyInto(*out) } + if in.OAuth != nil { + in, out := &in.OAuth, &out.OAuth + *out = new(SASLOAuth) + (*in).DeepCopyInto(*out) + } return } @@ -2204,6 +2209,32 @@ func (in *SASL) DeepCopy() *SASL { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SASLOAuth) DeepCopyInto(out *SASLOAuth) { + *out = *in + if in.ClientID != nil { + in, out := &in.ClientID, &out.ClientID + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } + if in.ClientSecret != nil { + in, out := &in.ClientSecret, &out.ClientSecret + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SASLOAuth. +func (in *SASLOAuth) DeepCopy() *SASLOAuth { + if in == nil { + return nil + } + out := new(SASLOAuth) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SASLPlain) DeepCopyInto(out *SASLPlain) { *out = *in diff --git a/pkg/apis/numaflow/v1alpha1/zz_generated.openapi.go b/pkg/apis/numaflow/v1alpha1/zz_generated.openapi.go index 3b20e1948..871bd095d 100644 --- a/pkg/apis/numaflow/v1alpha1/zz_generated.openapi.go +++ b/pkg/apis/numaflow/v1alpha1/zz_generated.openapi.go @@ -97,6 +97,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.RetryStrategy": schema_pkg_apis_numaflow_v1alpha1_RetryStrategy(ref), "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.RollingUpdateStrategy": schema_pkg_apis_numaflow_v1alpha1_RollingUpdateStrategy(ref), "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.SASL": schema_pkg_apis_numaflow_v1alpha1_SASL(ref), + "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.SASLOAuth": schema_pkg_apis_numaflow_v1alpha1_SASLOAuth(ref), "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.SASLPlain": schema_pkg_apis_numaflow_v1alpha1_SASLPlain(ref), "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Scale": schema_pkg_apis_numaflow_v1alpha1_Scale(ref), "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.ServingSource": schema_pkg_apis_numaflow_v1alpha1_ServingSource(ref), @@ -4595,12 +4596,53 @@ func schema_pkg_apis_numaflow_v1alpha1_SASL(ref common.ReferenceCallback) common Ref: ref("github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.SASLPlain"), }, }, + "oauth": { + SchemaProps: spec.SchemaProps{ + Description: "OAuth contains the oauth config", + Ref: ref("github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.SASLOAuth"), + }, + }, }, Required: []string{"mechanism"}, }, }, Dependencies: []string{ - "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.GSSAPI", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.SASLPlain"}, + "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.GSSAPI", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.SASLOAuth", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.SASLPlain"}, + } +} + +func schema_pkg_apis_numaflow_v1alpha1_SASLOAuth(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "clientID": { + SchemaProps: spec.SchemaProps{ + Description: "ClientID refers to the secret that contains the client id", + Ref: ref("k8s.io/api/core/v1.SecretKeySelector"), + }, + }, + "clientSecret": { + SchemaProps: spec.SchemaProps{ + Description: "ClientSecret refers to the secret that contains the client secret", + Ref: ref("k8s.io/api/core/v1.SecretKeySelector"), + }, + }, + "tokenEndpoint": { + SchemaProps: spec.SchemaProps{ + Description: "TokenEndpoint refers to the token endpoint", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"clientID", "clientSecret", "tokenEndpoint"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.SecretKeySelector"}, } } diff --git a/pkg/shared/util/sasl_config.go b/pkg/shared/util/sasl_config.go index fdf206792..556d45180 100644 --- a/pkg/shared/util/sasl_config.go +++ b/pkg/shared/util/sasl_config.go @@ -17,6 +17,7 @@ limitations under the License. package util import ( + "context" "crypto/sha256" "crypto/sha512" "fmt" @@ -25,6 +26,9 @@ import ( "github.com/IBM/sarama" dfv1 "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1" "github.com/xdg-go/scram" + "golang.org/x/oauth2" + "golang.org/x/oauth2/clientcredentials" + corev1 "k8s.io/api/core/v1" ) @@ -102,6 +106,15 @@ func getSASLStrategy(saslConfig *dfv1.SASL, strategy volumeReader) (*struct { } config.Net.SASL.Handshake = scram.Handshake } + case dfv1.SASLTypeOAuth: + if oauth := saslConfig.OAuth; oauth != nil { + config.Net.SASL.Enable = true + config.Net.SASL.Mechanism = sarama.SASLTypeOAuth + err := setOAuthClient(config, oauth, strategy) + if err != nil { + return nil, fmt.Errorf("error constructing oauth client, %w", err) + } + } default: return nil, fmt.Errorf("SASL mechanism not supported: %s", *saslConfig.Mechanism) } @@ -226,3 +239,54 @@ func (x *XDGSCRAMClient) Step(challenge string) (response string, err error) { func (x *XDGSCRAMClient) Done() bool { return x.ClientConversation.Done() } + +func setOAuthClient(config *sarama.Config, oauthconfig *dfv1.SASLOAuth, strategy volumeReader) error { + if oauthconfig.ClientID == nil { + return fmt.Errorf("clientID is required for SASL/OAuth") + } + + clientID, err := strategy.getSecretFromVolume(oauthconfig.ClientID) + if err != nil { + return err + } + + if oauthconfig.ClientSecret == nil { + return fmt.Errorf("clientSecret is required for SASL/OAuth") + } + + clientSecret, err := strategy.getSecretFromVolume(oauthconfig.ClientSecret) + if err != nil { + return err + } + + config.Net.SASL.TokenProvider = NewTokenProvider(clientID, clientSecret, oauthconfig.TokenEndpoint) + + return nil +} + +type OAUTHTokenProvider struct { + tokenSource oauth2.TokenSource +} + +func NewTokenProvider(clientID, clientSecret, tokenEndpoint string) sarama.AccessTokenProvider { + cfg := clientcredentials.Config{ + ClientID: clientID, + ClientSecret: clientSecret, + TokenURL: tokenEndpoint, + } + + return &OAUTHTokenProvider{ + tokenSource: cfg.TokenSource(context.Background()), + } +} + +func (o *OAUTHTokenProvider) Token() (*sarama.AccessToken, error) { + t, err := o.tokenSource.Token() + if err != nil { + return nil, err + } + + return &sarama.AccessToken{ + Token: t.AccessToken, + }, nil +} diff --git a/pkg/shared/util/sasl_config_test.go b/pkg/shared/util/sasl_config_test.go index 219338a9a..cfe5c2eb8 100644 --- a/pkg/shared/util/sasl_config_test.go +++ b/pkg/shared/util/sasl_config_test.go @@ -41,6 +41,14 @@ func TestSaslConfiguration(t *testing.T) { objectName: "password-secret-name", key: "password", }: "password", + { + objectName: "oauth-client-secret", + key: "clientid", + }: "clientid", + { + objectName: "oauth-client-secret", + key: "clientsecret", + }: "clientsecret", }, } @@ -60,6 +68,22 @@ func TestSaslConfiguration(t *testing.T) { Handshake: true, } + oauthcredentials := &dfv1.SASLOAuth{ + ClientID: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "oauth-client-secret", + }, + Key: "clientid", + }, + ClientSecret: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "oauth-client-secret", + }, + Key: "clientsecret", + }, + TokenEndpoint: "https://token-endpoint/token", + } + t.Run("Plain produces right values", func(t *testing.T) { plain := dfv1.SASLTypePlaintext config, err := getSASLStrategy(&dfv1.SASL{ @@ -109,6 +133,18 @@ func TestSaslConfiguration(t *testing.T) { assert.Nil(t, config) }) + + t.Run("OAuth produces right values", func(t *testing.T) { + oauth := dfv1.SASLTypeOAuth + config, err := getSASLStrategy(&dfv1.SASL{ + Mechanism: &oauth, + OAuth: oauthcredentials, + }, mockedVolumes) + assert.NoError(t, err) + assert.Equal(t, true, config.Enable) + assert.Equal(t, sarama.SASLTypeOAuth, string(config.Mechanism)) + assert.NotNil(t, config.TokenProvider) + }) } func TestGetGSSAPIConfig_InvalidAuthType(t *testing.T) { @@ -172,7 +208,6 @@ type mockGSSAPI struct { } func TestGetGSSAPIConfig(t *testing.T) { - authType := dfv1.KRB5UserAuth tests := []struct { name string diff --git a/rust/numaflow-models/src/models/mod.rs b/rust/numaflow-models/src/models/mod.rs index 7d890089a..3783d1fc5 100644 --- a/rust/numaflow-models/src/models/mod.rs +++ b/rust/numaflow-models/src/models/mod.rs @@ -142,6 +142,8 @@ pub mod sasl; pub use self::sasl::Sasl; pub mod sasl_plain; pub use self::sasl_plain::SaslPlain; +pub mod saslo_auth; +pub use self::saslo_auth::SasloAuth; pub mod scale; pub use self::scale::Scale; pub mod serving_source; diff --git a/rust/numaflow-models/src/models/sasl.rs b/rust/numaflow-models/src/models/sasl.rs index a2941f2e9..adc2c80e2 100644 --- a/rust/numaflow-models/src/models/sasl.rs +++ b/rust/numaflow-models/src/models/sasl.rs @@ -23,6 +23,8 @@ pub struct Sasl { /// SASL mechanism to use #[serde(rename = "mechanism")] pub mechanism: String, + #[serde(rename = "oauth", skip_serializing_if = "Option::is_none")] + pub oauth: Option>, #[serde(rename = "plain", skip_serializing_if = "Option::is_none")] pub plain: Option>, #[serde(rename = "scramsha256", skip_serializing_if = "Option::is_none")] @@ -36,6 +38,7 @@ impl Sasl { Sasl { gssapi: None, mechanism, + oauth: None, plain: None, scramsha256: None, scramsha512: None, diff --git a/rust/numaflow-models/src/models/saslo_auth.rs b/rust/numaflow-models/src/models/saslo_auth.rs new file mode 100644 index 000000000..506002f65 --- /dev/null +++ b/rust/numaflow-models/src/models/saslo_auth.rs @@ -0,0 +1,42 @@ +/* +Copyright 2022 The Numaproj Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by Openapi Generator. DO NOT EDIT. + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct SasloAuth { + #[serde(rename = "clientID")] + pub client_id: k8s_openapi::api::core::v1::SecretKeySelector, + #[serde(rename = "clientSecret")] + pub client_secret: k8s_openapi::api::core::v1::SecretKeySelector, + /// TokenEndpoint refers to the token endpoint + #[serde(rename = "tokenEndpoint")] + pub token_endpoint: String, +} + +impl SasloAuth { + pub fn new( + client_id: k8s_openapi::api::core::v1::SecretKeySelector, + client_secret: k8s_openapi::api::core::v1::SecretKeySelector, + token_endpoint: String, + ) -> SasloAuth { + SasloAuth { + client_id, + client_secret, + token_endpoint, + } + } +} From fd4a0aaca263c82a92340ac00f28bd77feba9f27 Mon Sep 17 00:00:00 2001 From: Yashash H L Date: Wed, 15 Jan 2025 09:55:41 +0530 Subject: [PATCH 7/9] feat: expose pending messages metric in async pipeline (#2330) Signed-off-by: Yashash H L Signed-off-by: Vigith Maurice Co-authored-by: Vigith Maurice --- rust/numaflow-core/src/mapper/map.rs | 27 +- .../src/mapper/map/user_defined.rs | 2 +- rust/numaflow-core/src/message.rs | 5 +- rust/numaflow-core/src/metrics.rs | 309 ++++++++++++++---- rust/numaflow-core/src/monovertex.rs | 8 +- rust/numaflow-core/src/pipeline.rs | 37 ++- .../src/pipeline/isb/jetstream/reader.rs | 14 + rust/numaflow-core/src/shared/metrics.rs | 8 +- rust/numaflow-core/src/sink.rs | 7 +- rust/numaflow-core/src/source.rs | 3 +- rust/numaflow-core/src/source/serving.rs | 11 +- rust/numaflow-core/src/source/user_defined.rs | 12 +- .../src/transformer/user_defined.rs | 12 +- rust/serving/src/app.rs | 6 +- rust/serving/src/config.rs | 3 +- rust/serving/src/lib.rs | 5 +- rust/serving/src/source.rs | 3 +- 17 files changed, 348 insertions(+), 124 deletions(-) diff --git a/rust/numaflow-core/src/mapper/map.rs b/rust/numaflow-core/src/mapper/map.rs index 8c279376a..36918d62f 100644 --- a/rust/numaflow-core/src/mapper/map.rs +++ b/rust/numaflow-core/src/mapper/map.rs @@ -1,3 +1,13 @@ +use std::sync::Arc; +use std::time::Duration; + +use numaflow_pb::clients::map::map_client::MapClient; +use tokio::sync::{mpsc, oneshot, OwnedSemaphorePermit, Semaphore}; +use tokio::task::JoinHandle; +use tokio_stream::wrappers::ReceiverStream; +use tokio_stream::StreamExt; +use tonic::transport::Channel; + use crate::config::pipeline::map::MapMode; use crate::error; use crate::error::Error; @@ -6,14 +16,6 @@ use crate::mapper::map::user_defined::{ }; use crate::message::Message; use crate::tracker::TrackerHandle; -use numaflow_pb::clients::map::map_client::MapClient; -use std::sync::Arc; -use std::time::Duration; -use tokio::sync::{mpsc, oneshot, OwnedSemaphorePermit, Semaphore}; -use tokio::task::JoinHandle; -use tokio_stream::wrappers::ReceiverStream; -use tokio_stream::StreamExt; -use tonic::transport::Channel; pub(super) mod user_defined; /// UnaryActorMessage is a message that is sent to the UnaryMapperActor. @@ -481,12 +483,8 @@ impl MapHandle { #[cfg(test)] mod tests { - use super::*; - use crate::Result; use std::time::Duration; - use crate::message::{MessageID, Offset, StringOffset}; - use crate::shared::grpc::create_rpc_channel; use numaflow::mapstream; use numaflow::{batchmap, map}; use numaflow_pb::clients::map::map_client::MapClient; @@ -494,6 +492,11 @@ mod tests { use tokio::sync::mpsc::Sender; use tokio::sync::oneshot; + use super::*; + use crate::message::{MessageID, Offset, StringOffset}; + use crate::shared::grpc::create_rpc_channel; + use crate::Result; + struct SimpleMapper; #[tonic::async_trait] diff --git a/rust/numaflow-core/src/mapper/map/user_defined.rs b/rust/numaflow-core/src/mapper/map/user_defined.rs index 0799eb654..d7abfff10 100644 --- a/rust/numaflow-core/src/mapper/map/user_defined.rs +++ b/rust/numaflow-core/src/mapper/map/user_defined.rs @@ -430,12 +430,12 @@ impl UserDefinedStreamMap { #[cfg(test)] mod tests { - use numaflow::mapstream; use std::error::Error; use std::sync::Arc; use std::time::Duration; use numaflow::batchmap::Server; + use numaflow::mapstream; use numaflow::{batchmap, map}; use numaflow_pb::clients::map::map_client::MapClient; use tempfile::TempDir; diff --git a/rust/numaflow-core/src/message.rs b/rust/numaflow-core/src/message.rs index fe20613da..86259bf8f 100644 --- a/rust/numaflow-core/src/message.rs +++ b/rust/numaflow-core/src/message.rs @@ -218,14 +218,15 @@ impl TryFrom for Message { #[cfg(test)] mod tests { - use crate::error::Result; + use std::collections::HashMap; + use chrono::TimeZone; use numaflow_pb::objects::isb::{ Body, Header, Message as ProtoMessage, MessageId, MessageInfo, }; - use std::collections::HashMap; use super::*; + use crate::error::Result; #[test] fn test_offset_display() { diff --git a/rust/numaflow-core/src/metrics.rs b/rust/numaflow-core/src/metrics.rs index 2a672ec31..d63b82de7 100644 --- a/rust/numaflow-core/src/metrics.rs +++ b/rust/numaflow-core/src/metrics.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; use std::iter; use std::net::SocketAddr; use std::sync::{Arc, OnceLock}; @@ -29,6 +29,7 @@ use tonic::Request; use tracing::{debug, error, info}; use crate::config::{get_pipeline_name, get_vertex_name, get_vertex_replica}; +use crate::pipeline::isb::jetstream::reader::JetstreamReader; use crate::source::Source; use crate::Error; @@ -73,8 +74,10 @@ const SINK_WRITE_TOTAL: &str = "write"; const DROPPED_TOTAL: &str = "dropped"; const FALLBACK_SINK_WRITE_TOTAL: &str = "write"; -// pending as gauge +// pending as gauge for mvtx (these metric names are hardcoded in the auto-scaler) const PENDING: &str = "pending"; +// pending as gauge for pipeline +const VERTEX_PENDING: &str = "pending_messages"; // processing times as timers const E2E_TIME: &str = "processing_time"; @@ -204,6 +207,7 @@ pub(crate) struct MonoVtxMetrics { pub(crate) struct PipelineMetrics { pub(crate) forwarder: PipelineForwarderMetrics, pub(crate) isb: PipelineISBMetrics, + pub(crate) pending: Family, Gauge>, } /// Family of metrics for the sink @@ -232,7 +236,6 @@ pub(crate) struct PipelineForwarderMetrics { pub(crate) write_time: Family, Histogram>, pub(crate) read_bytes_total: Family, Counter>, pub(crate) processed_time: Family, Histogram>, - pub(crate) pending: Family, Gauge>, pub(crate) dropped_total: Family, Counter>, } @@ -398,7 +401,6 @@ impl PipelineMetrics { ack_time: Family::, Histogram>::new_with_constructor(|| { Histogram::new(exponential_buckets_range(100.0, 60000000.0 * 15.0, 10)) }), - pending: Family::, Gauge>::default(), write_total: Family::, Counter>::default(), write_time: Family::, Histogram>::new_with_constructor( || Histogram::new(exponential_buckets_range(100.0, 60000000.0 * 15.0, 10)), @@ -411,6 +413,7 @@ impl PipelineMetrics { Histogram::new(exponential_buckets_range(100.0, 60000000.0 * 15.0, 10)) }), }, + pending: Family::, Gauge>::default(), }; let mut registry = global_registry().registry.lock(); @@ -446,11 +449,6 @@ impl PipelineMetrics { "Time taken to ack data", metrics.forwarder.ack_time.clone(), ); - forwarder_registry.register( - PENDING, - "Number of pending messages", - metrics.forwarder.pending.clone(), - ); forwarder_registry.register( SINK_WRITE_TOTAL, "Total number of Data Messages Written", @@ -466,6 +464,13 @@ impl PipelineMetrics { "Time taken to write data", metrics.forwarder.write_time.clone(), ); + + let vertex_registry = registry.sub_registry_with_prefix("vertex"); + vertex_registry.register( + VERTEX_PENDING, + "Total number of pending messages", + metrics.pending.clone(), + ); metrics } } @@ -710,14 +715,21 @@ struct TimestampedPending { timestamp: std::time::Instant, } +#[derive(Clone)] +pub(crate) enum LagReader { + Source(Source), + // TODO: Arc<[T]> + ISB(Vec), // multiple partitions +} + /// PendingReader is responsible for periodically checking the lag of the reader /// and exposing the metrics. It maintains a list of pending stats and ensures that /// only the most recent entries are kept. pub(crate) struct PendingReader { - lag_reader: Source, + lag_reader: LagReader, lag_checking_interval: Duration, refresh_interval: Duration, - pending_stats: Arc>>, + pending_stats: Arc>>>, lookback_seconds: u16, } @@ -728,14 +740,14 @@ pub(crate) struct PendingReaderTasks { /// PendingReaderBuilder is used to build a [LagReader] instance. pub(crate) struct PendingReaderBuilder { - lag_reader: Source, + lag_reader: LagReader, lag_checking_interval: Option, refresh_interval: Option, lookback_seconds: Option, } impl PendingReaderBuilder { - pub(crate) fn new(lag_reader: Source) -> Self { + pub(crate) fn new(lag_reader: LagReader) -> Self { Self { lag_reader, lag_checking_interval: None, @@ -760,6 +772,22 @@ impl PendingReaderBuilder { } pub(crate) fn build(self) -> PendingReader { + let mut pending_map = HashMap::new(); + match &self.lag_reader { + LagReader::Source(_) => { + pending_map.insert("source".to_string(), Vec::with_capacity(MAX_PENDING_STATS)); + } + LagReader::ISB(readers) => { + // need a lag reader per partition + for reader in readers { + pending_map.insert( + reader.name().to_string(), + Vec::with_capacity(MAX_PENDING_STATS), + ); + } + } + } + PendingReader { lag_reader: self.lag_reader, lag_checking_interval: self @@ -769,7 +797,7 @@ impl PendingReaderBuilder { .refresh_interval .unwrap_or_else(|| Duration::from_secs(5)), lookback_seconds: self.lookback_seconds.unwrap_or(120), - pending_stats: Arc::new(Mutex::new(Vec::with_capacity(MAX_PENDING_STATS))), + pending_stats: Arc::new(Mutex::new(pending_map)), } } } @@ -783,14 +811,14 @@ impl PendingReader { /// /// Dropping the PendingReaderTasks will abort the background tasks. pub async fn start(&self, is_mono_vertex: bool) -> PendingReaderTasks { - let pending_reader = self.lag_reader.clone(); let lag_checking_interval = self.lag_checking_interval; let refresh_interval = self.refresh_interval; let pending_stats = Arc::clone(&self.pending_stats); let lookback_seconds = self.lookback_seconds; + let lag_reader = self.lag_reader.clone(); let buildup_handle = tokio::spawn(async move { - build_pending_info(pending_reader, lag_checking_interval, pending_stats).await; + build_pending_info(lag_reader, lag_checking_interval, pending_stats).await; }); let pending_stats = Arc::clone(&self.pending_stats); @@ -821,45 +849,89 @@ impl Drop for PendingReaderTasks { /// Periodically checks the pending messages from the source client and build the pending stats. async fn build_pending_info( - source: Source, + mut lag_reader: LagReader, lag_checking_interval: Duration, - pending_stats: Arc>>, + pending_stats: Arc>>>, ) { let mut ticker = time::interval(lag_checking_interval); + loop { ticker.tick().await; - match fetch_pending(&source).await { - Ok(pending) => { - if pending != -1 { - let mut stats = pending_stats.lock().await; - stats.push(TimestampedPending { - pending, - timestamp: std::time::Instant::now(), - }); - let n = stats.len(); - // Ensure only the most recent MAX_PENDING_STATS entries are kept - if n >= MAX_PENDING_STATS { - stats.drain(0..(n - MAX_PENDING_STATS)); + + match &mut lag_reader { + LagReader::Source(source) => { + match fetch_source_pending(&source).await { + Ok(pending) => { + if pending != -1 { + let mut stats = pending_stats.lock().await; + stats.get_mut("source").unwrap().push(TimestampedPending { + pending, + timestamp: std::time::Instant::now(), + }); + let n = stats.len(); + // Ensure only the most recent MAX_PENDING_STATS entries are kept + if n >= MAX_PENDING_STATS { + stats + .get_mut("source") + .unwrap() + .drain(0..(n - MAX_PENDING_STATS)); + } + } + } + Err(err) => { + error!("Failed to get pending messages: {:?}", err); } } } - Err(err) => { - error!("Failed to get pending messages: {:?}", err); + + LagReader::ISB(readers) => { + for mut reader in readers { + match fetch_isb_pending(&mut reader).await { + Ok(pending) => { + if pending != -1 { + let mut stats = pending_stats.lock().await; + stats + .get_mut(reader.name()) + .unwrap() + .push(TimestampedPending { + pending, + timestamp: std::time::Instant::now(), + }); + let n = stats.len(); + // Ensure only the most recent MAX_PENDING_STATS entries are kept + if n >= MAX_PENDING_STATS { + stats + .get_mut(reader.name()) + .unwrap() + .drain(0..(n - MAX_PENDING_STATS)); + } + } + } + Err(err) => { + error!("Failed to get pending messages: {:?}", err); + } + } + } } } } } -async fn fetch_pending(lag_reader: &Source) -> crate::error::Result { +async fn fetch_source_pending(lag_reader: &Source) -> crate::error::Result { let response: i64 = lag_reader.pending().await?.map_or(-1, |p| p as i64); // default to -1(unavailable) Ok(response) } +async fn fetch_isb_pending(reader: &mut JetstreamReader) -> crate::error::Result { + let response: i64 = reader.pending().await?.map_or(-1, |p| p as i64); // default to -1(unavailable) + Ok(response) +} + // Periodically exposes the pending metrics by calculating the average pending messages over different intervals. async fn expose_pending_metrics( is_mono_vertex: bool, refresh_interval: Duration, - pending_stats: Arc>>, + pending_map: Arc>>>, lookback_seconds: u16, ) { let mut ticker = time::interval(refresh_interval); @@ -877,46 +949,46 @@ async fn expose_pending_metrics( loop { ticker.tick().await; - for (label, seconds) in lookback_seconds_map { - let pending = calculate_pending(seconds as i64, &pending_stats).await; - if pending != -1 { - let mut metric_labels = mvtx_forward_metric_labels().clone(); - metric_labels.push((PENDING_PERIOD_LABEL.to_string(), label.to_string())); - pending_info.insert(label, pending); - if is_mono_vertex { - monovertex_metrics() - .pending - .get_or_create(&metric_labels) - .set(pending); - } else { - pipeline_metrics() - .forwarder - .pending - .get_or_create(&metric_labels) - .set(pending); + for (name, pending_stats) in pending_map.lock().await.iter() { + for (label, seconds) in lookback_seconds_map { + let pending = calculate_pending(seconds as i64, pending_stats).await; + if pending != -1 { + pending_info.insert(label, pending); + if is_mono_vertex { + let mut metric_labels = mvtx_forward_metric_labels().clone(); + metric_labels.push((PENDING_PERIOD_LABEL.to_string(), label.to_string())); + monovertex_metrics() + .pending + .get_or_create(&metric_labels) + .set(pending); + } else { + let mut metric_labels = + pipeline_forward_metric_labels(name, Some(name)).clone(); + metric_labels.push((PENDING_PERIOD_LABEL.to_string(), label.to_string())); + pipeline_metrics() + .pending + .get_or_create(&metric_labels) + .set(pending); + } } } - } - // skip for those the pending is not implemented - if !pending_info.is_empty() { - info!("Pending messages {:?}", pending_info); - pending_info.clear(); + // skip for those the pending is not implemented + if !pending_info.is_empty() { + info!("Pending messages {:?}", pending_info); + pending_info.clear(); + } } } } /// Calculate the average pending messages over the last `seconds` seconds. -async fn calculate_pending( - seconds: i64, - pending_stats: &Arc>>, -) -> i64 { +async fn calculate_pending(seconds: i64, pending_stats: &Vec) -> i64 { let mut result = -1; let mut total = 0; let mut num = 0; let now = std::time::Instant::now(); - let stats = pending_stats.lock().await; - for item in stats.iter().rev() { + for item in pending_stats.iter().rev() { if now.duration_since(item.timestamp).as_secs() < seconds as u64 { total += item.pending; num += 1; @@ -1098,8 +1170,11 @@ mod tests { } #[tokio::test] - async fn test_expose_pending_metrics() { - let pending_stats = Arc::new(Mutex::new(Vec::with_capacity(MAX_PENDING_STATS))); + async fn test_expose_pending_metrics_for_source() { + let mut pending_map = HashMap::new(); + pending_map.insert("source".to_string(), Vec::with_capacity(MAX_PENDING_STATS)); + + let pending_stats = Arc::new(Mutex::new(pending_map)); let refresh_interval = Duration::from_secs(1); let lookback_seconds = 120; @@ -1107,19 +1182,20 @@ mod tests { // The array will be sorted by the timestamp with the most recent last. { let mut pending_stats = pending_stats.lock().await; - pending_stats.push(TimestampedPending { + let pending_vec = pending_stats.get_mut("source").unwrap(); + pending_vec.push(TimestampedPending { pending: 15, timestamp: Instant::now() - Duration::from_secs(150), }); - pending_stats.push(TimestampedPending { + pending_vec.push(TimestampedPending { pending: 30, timestamp: Instant::now() - Duration::from_secs(70), }); - pending_stats.push(TimestampedPending { + pending_vec.push(TimestampedPending { pending: 20, timestamp: Instant::now() - Duration::from_secs(30), }); - pending_stats.push(TimestampedPending { + pending_vec.push(TimestampedPending { pending: 10, timestamp: Instant::now(), }); @@ -1155,6 +1231,103 @@ mod tests { } assert_eq!(stored_values, [15, 20, 18, 18]); } + + #[tokio::test] + async fn test_expose_pending_metrics_for_isb() { + let mut pending_map = HashMap::new(); + pending_map.insert("stream1".to_string(), Vec::with_capacity(MAX_PENDING_STATS)); + pending_map.insert("stream2".to_string(), Vec::with_capacity(MAX_PENDING_STATS)); + + let pending_stats = Arc::new(Mutex::new(pending_map)); + let refresh_interval = Duration::from_secs(1); + let lookback_seconds = 120; + + // Populate pending_stats with some values. + // The array will be sorted by the timestamp with the most recent last. + { + let mut pending_stats = pending_stats.lock().await; + let pending_vec = pending_stats.get_mut("stream1").unwrap(); + pending_vec.push(TimestampedPending { + pending: 15, + timestamp: Instant::now() - Duration::from_secs(150), + }); + pending_vec.push(TimestampedPending { + pending: 30, + timestamp: Instant::now() - Duration::from_secs(70), + }); + pending_vec.push(TimestampedPending { + pending: 20, + timestamp: Instant::now() - Duration::from_secs(30), + }); + pending_vec.push(TimestampedPending { + pending: 10, + timestamp: Instant::now(), + }); + + let pending_vec = pending_stats.get_mut("stream2").unwrap(); + pending_vec.push(TimestampedPending { + pending: 15, + timestamp: Instant::now() - Duration::from_secs(150), + }); + pending_vec.push(TimestampedPending { + pending: 30, + timestamp: Instant::now() - Duration::from_secs(70), + }); + pending_vec.push(TimestampedPending { + pending: 20, + timestamp: Instant::now() - Duration::from_secs(30), + }); + pending_vec.push(TimestampedPending { + pending: 10, + timestamp: Instant::now(), + }); + } + + tokio::spawn({ + let pending_stats = Arc::clone(&pending_stats); + async move { + expose_pending_metrics(false, refresh_interval, pending_stats, lookback_seconds) + .await; + } + }); + + // We use tokio::time::interval() as the ticker in the expose_pending_metrics() function. + // The first tick happens immediately, so we don't need to wait for the refresh_interval for the first iteration to complete. + tokio::time::sleep(Duration::from_millis(50)).await; + + let lookback_seconds_map: [(&str, u16); 4] = + [("1m", 60), ("default", 120), ("5m", 300), ("15m", 900)]; + + // Get the stored values for all time intervals + // We will store the values corresponding to the labels (from lookback_seconds_map) "1m", "default", "5m", "15" in the same order in this array + let mut stored_values_stream_one: [i64; 4] = [0; 4]; + let mut stored_values_stream_two: [i64; 4] = [0; 4]; + + { + for (i, (label, _)) in lookback_seconds_map.iter().enumerate() { + let mut metric_labels = + pipeline_forward_metric_labels("stream1", Some("stream1")).clone(); + metric_labels.push((PENDING_PERIOD_LABEL.to_string(), label.to_string())); + let guage = pipeline_metrics() + .pending + .get_or_create(&metric_labels) + .get(); + stored_values_stream_one[i] = guage; + + let mut metric_labels = + pipeline_forward_metric_labels("stream2", Some("stream2")).clone(); + metric_labels.push((PENDING_PERIOD_LABEL.to_string(), label.to_string())); + let guage = pipeline_metrics() + .pending + .get_or_create(&metric_labels) + .get(); + stored_values_stream_two[i] = guage; + } + } + assert_eq!(stored_values_stream_one, [15, 20, 18, 18]); + assert_eq!(stored_values_stream_two, [15, 20, 18, 18]); + } + #[test] fn test_exponential_buckets_range_basic() { let min = 1.0; diff --git a/rust/numaflow-core/src/monovertex.rs b/rust/numaflow-core/src/monovertex.rs index 1518a3c9f..4d4f109d4 100644 --- a/rust/numaflow-core/src/monovertex.rs +++ b/rust/numaflow-core/src/monovertex.rs @@ -5,6 +5,7 @@ use tracing::info; use crate::config::is_mono_vertex; use crate::config::monovertex::MonovertexConfig; use crate::error::{self}; +use crate::metrics::LagReader; use crate::shared::create_components; use crate::sink::SinkWriter; use crate::source::Source; @@ -81,8 +82,11 @@ async fn start( cln_token: CancellationToken, ) -> error::Result<()> { // start the pending reader to publish pending metrics - let pending_reader = - shared::metrics::create_pending_reader(&mvtx_config.metrics_config, source.clone()).await; + let pending_reader = shared::metrics::create_pending_reader( + &mvtx_config.metrics_config, + LagReader::Source(source.clone()), + ) + .await; let _pending_reader_handle = pending_reader.start(is_mono_vertex()).await; let mut forwarder_builder = ForwarderBuilder::new(source, sink, cln_token); diff --git a/rust/numaflow-core/src/pipeline.rs b/rust/numaflow-core/src/pipeline.rs index d2cb77091..54dcd36ce 100644 --- a/rust/numaflow-core/src/pipeline.rs +++ b/rust/numaflow-core/src/pipeline.rs @@ -6,10 +6,10 @@ use futures::future::try_join_all; use tokio_util::sync::CancellationToken; use tracing::info; -use crate::config::pipeline; use crate::config::pipeline::map::MapVtxConfig; use crate::config::pipeline::{PipelineConfig, SinkVtxConfig, SourceVtxConfig}; -use crate::metrics::{PipelineContainerState, UserDefinedContainerState}; +use crate::config::{is_mono_vertex, pipeline}; +use crate::metrics::{LagReader, PipelineContainerState, UserDefinedContainerState}; use crate::pipeline::forwarder::source_forwarder; use crate::pipeline::isb::jetstream::reader::JetstreamReader; use crate::pipeline::isb::jetstream::writer::JetstreamWriter; @@ -18,10 +18,10 @@ use crate::shared::create_components; use crate::shared::create_components::create_sink_writer; use crate::shared::metrics::start_metrics_server; use crate::tracker::TrackerHandle; -use crate::{error, Result}; +use crate::{error, shared, Result}; mod forwarder; -mod isb; +pub(crate) mod isb; /// Starts the appropriate forwarder based on the pipeline configuration. pub(crate) async fn start_forwarder( @@ -77,6 +77,13 @@ async fn start_source_forwarder( ) .await?; + let pending_reader = shared::metrics::create_pending_reader( + &config.metrics_config, + LagReader::Source(source.clone()), + ) + .await; + let _pending_reader_handle = pending_reader.start(is_mono_vertex()).await; + start_metrics_server( config.metrics_config.clone(), UserDefinedContainerState::Pipeline(PipelineContainerState::Source(( @@ -117,6 +124,8 @@ async fn start_map_forwarder( // Create buffer writers and buffer readers let mut forwarder_components = vec![]; let mut mapper_grpc_client = None; + let mut isb_lag_readers = vec![]; + for stream in reader_config.streams.clone() { let tracker_handle = TrackerHandle::new(); @@ -129,6 +138,8 @@ async fn start_map_forwarder( ) .await?; + isb_lag_readers.push(buffer_reader.clone()); + let (mapper, mapper_rpc_client) = create_components::create_mapper( config.batch_size, config.read_timeout, @@ -152,6 +163,13 @@ async fn start_map_forwarder( forwarder_components.push((buffer_reader, buffer_writer, mapper)); } + let pending_reader = shared::metrics::create_pending_reader( + &config.metrics_config, + LagReader::ISB(isb_lag_readers), + ) + .await; + let _pending_reader_handle = pending_reader.start(is_mono_vertex()).await; + start_metrics_server( config.metrics_config.clone(), UserDefinedContainerState::Pipeline(PipelineContainerState::Map(mapper_grpc_client)), @@ -228,6 +246,13 @@ async fn start_sink_forwarder( sink_writers.push((sink_writer, sink_grpc_client, fb_sink_grpc_client)); } + let pending_reader = shared::metrics::create_pending_reader( + &config.metrics_config, + LagReader::ISB(buffer_readers.clone()), + ) + .await; + let _pending_reader_handle = pending_reader.start(is_mono_vertex()).await; + // Start the metrics server with one of the clients if let Some((_, sink, fb_sink)) = sink_writers.first() { start_metrics_server( @@ -324,7 +349,6 @@ async fn create_js_context(config: pipeline::isb::jetstream::ClientConfig) -> Re #[cfg(test)] mod tests { - use crate::pipeline::pipeline::map::MapMode; use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; @@ -345,6 +369,7 @@ mod tests { use crate::config::pipeline::PipelineConfig; use crate::pipeline::pipeline::isb; use crate::pipeline::pipeline::isb::{BufferReaderConfig, BufferWriterConfig}; + use crate::pipeline::pipeline::map::MapMode; use crate::pipeline::pipeline::VertexType; use crate::pipeline::pipeline::{FromVertexConfig, ToVertexConfig}; use crate::pipeline::pipeline::{SinkVtxConfig, SourceVtxConfig}; @@ -420,7 +445,7 @@ mod tests { streams: streams .iter() .enumerate() - .map(|(i, stream_name)| (stream_name.to_string(), i as u16)) + .map(|(i, stream_name)| ((*stream_name).to_string(), i as u16)) .collect(), partitions: 5, max_length: 30000, diff --git a/rust/numaflow-core/src/pipeline/isb/jetstream/reader.rs b/rust/numaflow-core/src/pipeline/isb/jetstream/reader.rs index 79b8572ef..152004aed 100644 --- a/rust/numaflow-core/src/pipeline/isb/jetstream/reader.rs +++ b/rust/numaflow-core/src/pipeline/isb/jetstream/reader.rs @@ -252,6 +252,20 @@ impl JetstreamReader { } } } + + pub(crate) async fn pending(&mut self) -> Result> { + let x = self.consumer.info().await.map_err(|e| { + Error::ISB(format!( + "Failed to get consumer info for stream {}: {}", + self.stream_name, e + )) + })?; + Ok(Some(x.num_pending as usize + x.num_ack_pending)) + } + + pub(crate) fn name(&self) -> &'static str { + self.stream_name + } } impl fmt::Display for JetstreamReader { diff --git a/rust/numaflow-core/src/shared/metrics.rs b/rust/numaflow-core/src/shared/metrics.rs index dfc22401a..70e7523b1 100644 --- a/rust/numaflow-core/src/shared/metrics.rs +++ b/rust/numaflow-core/src/shared/metrics.rs @@ -6,9 +6,9 @@ use tracing::error; use crate::config::components::metrics::MetricsConfig; use crate::metrics::{ - start_metrics_https_server, PendingReader, PendingReaderBuilder, UserDefinedContainerState, + start_metrics_https_server, LagReader, PendingReader, PendingReaderBuilder, + UserDefinedContainerState, }; -use crate::source::Source; /// Starts the metrics server pub(crate) async fn start_metrics_server( @@ -31,9 +31,9 @@ pub(crate) async fn start_metrics_server( /// Creates a pending reader pub(crate) async fn create_pending_reader( metrics_config: &MetricsConfig, - lag_reader_grpc_client: Source, + lag_reader: LagReader, ) -> PendingReader { - PendingReaderBuilder::new(lag_reader_grpc_client) + PendingReaderBuilder::new(lag_reader) .lag_checking_interval(Duration::from_secs( metrics_config.lag_check_interval_in_secs.into(), )) diff --git a/rust/numaflow-core/src/sink.rs b/rust/numaflow-core/src/sink.rs index c60c57bd3..be24f2f32 100644 --- a/rust/numaflow-core/src/sink.rs +++ b/rust/numaflow-core/src/sink.rs @@ -713,15 +713,16 @@ impl Drop for SinkWriter { mod tests { use std::sync::Arc; - use super::*; - use crate::message::{Message, MessageID, Offset, ReadAck, StringOffset}; - use crate::shared::grpc::create_rpc_channel; use chrono::{TimeZone, Utc}; use numaflow::sink; use numaflow_pb::clients::sink::{SinkRequest, SinkResponse}; use tokio::time::Duration; use tokio_util::sync::CancellationToken; + use super::*; + use crate::message::{Message, MessageID, Offset, ReadAck, StringOffset}; + use crate::shared::grpc::create_rpc_channel; + struct SimpleSink; #[tonic::async_trait] impl sink::Sinker for SimpleSink { diff --git a/rust/numaflow-core/src/source.rs b/rust/numaflow-core/src/source.rs index a30fc9777..d48c37ab6 100644 --- a/rust/numaflow-core/src/source.rs +++ b/rust/numaflow-core/src/source.rs @@ -1,5 +1,6 @@ -use numaflow_pulsar::source::PulsarSource; use std::sync::Arc; + +use numaflow_pulsar::source::PulsarSource; use tokio::sync::OwnedSemaphorePermit; use tokio::sync::Semaphore; use tokio::sync::{mpsc, oneshot}; diff --git a/rust/numaflow-core/src/source/serving.rs b/rust/numaflow-core/src/source/serving.rs index 431cfbba3..5eeea4b05 100644 --- a/rust/numaflow-core/src/source/serving.rs +++ b/rust/numaflow-core/src/source/serving.rs @@ -2,13 +2,12 @@ use std::sync::Arc; pub(crate) use serving::ServingSource; +use super::{get_vertex_name, Message, Offset}; use crate::config::get_vertex_replica; use crate::message::{MessageID, StringOffset}; use crate::Error; use crate::Result; -use super::{get_vertex_name, Message, Offset}; - impl TryFrom for Message { type Error = Error; @@ -83,16 +82,16 @@ impl super::LagReader for ServingSource { #[cfg(test)] mod tests { - use crate::{ - message::{Message, MessageID, Offset, StringOffset}, - source::{SourceAcker, SourceReader}, - }; use std::{collections::HashMap, sync::Arc, time::Duration}; use bytes::Bytes; use serving::{ServingSource, Settings}; use super::get_vertex_replica; + use crate::{ + message::{Message, MessageID, Offset, StringOffset}, + source::{SourceAcker, SourceReader}, + }; type Result = std::result::Result>; diff --git a/rust/numaflow-core/src/source/user_defined.rs b/rust/numaflow-core/src/source/user_defined.rs index 5f274119b..66ac456dd 100644 --- a/rust/numaflow-core/src/source/user_defined.rs +++ b/rust/numaflow-core/src/source/user_defined.rs @@ -1,3 +1,6 @@ +use std::sync::Arc; +use std::time::Duration; + use base64::prelude::BASE64_STANDARD; use base64::Engine; use numaflow_pb::clients::source; @@ -5,8 +8,6 @@ use numaflow_pb::clients::source::source_client::SourceClient; use numaflow_pb::clients::source::{ read_request, read_response, AckRequest, AckResponse, ReadRequest, ReadResponse, }; -use std::sync::Arc; -use std::time::Duration; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; use tonic::transport::Channel; @@ -283,15 +284,16 @@ impl LagReader for UserDefinedSourceLagReader { mod tests { use std::collections::{HashMap, HashSet}; - use super::*; - use crate::message::IntOffset; - use crate::shared::grpc::{create_rpc_channel, prost_timestamp_from_utc}; use chrono::{TimeZone, Utc}; use numaflow::source; use numaflow::source::{Message, Offset, SourceReadRequest}; use numaflow_pb::clients::source::source_client::SourceClient; use tokio::sync::mpsc::Sender; + use super::*; + use crate::message::IntOffset; + use crate::shared::grpc::{create_rpc_channel, prost_timestamp_from_utc}; + struct SimpleSource { num: usize, yet_to_ack: std::sync::RwLock>, diff --git a/rust/numaflow-core/src/transformer/user_defined.rs b/rust/numaflow-core/src/transformer/user_defined.rs index 78518e4c0..f519c0b80 100644 --- a/rust/numaflow-core/src/transformer/user_defined.rs +++ b/rust/numaflow-core/src/transformer/user_defined.rs @@ -184,16 +184,18 @@ impl UserDefinedTransformer { #[cfg(test)] mod tests { - use super::*; - use crate::message::StringOffset; - use crate::shared::grpc::create_rpc_channel; - use chrono::{TimeZone, Utc}; - use numaflow::sourcetransform; use std::error::Error; use std::result::Result; use std::time::Duration; + + use chrono::{TimeZone, Utc}; + use numaflow::sourcetransform; use tempfile::TempDir; + use super::*; + use crate::message::StringOffset; + use crate::shared::grpc::create_rpc_channel; + struct NowCat; #[tonic::async_trait] diff --git a/rust/serving/src/app.rs b/rust/serving/src/app.rs index 82ef1ef62..330752ad6 100644 --- a/rust/serving/src/app.rs +++ b/rust/serving/src/app.rs @@ -253,14 +253,14 @@ mod tests { use std::sync::Arc; use axum::http::StatusCode; + use callback::state::State as CallbackState; + use tokio::sync::mpsc; use tower::ServiceExt; + use tracker::MessageGraph; use super::*; use crate::app::callback::store::memstore::InMemoryStore; use crate::Settings; - use callback::state::State as CallbackState; - use tokio::sync::mpsc; - use tracker::MessageGraph; const PIPELINE_SPEC_ENCODED: &str = "eyJ2ZXJ0aWNlcyI6W3sibmFtZSI6ImluIiwic291cmNlIjp7InNlcnZpbmciOnsiYXV0aCI6bnVsbCwic2VydmljZSI6dHJ1ZSwibXNnSURIZWFkZXJLZXkiOiJYLU51bWFmbG93LUlkIiwic3RvcmUiOnsidXJsIjoicmVkaXM6Ly9yZWRpczo2Mzc5In19fSwiY29udGFpbmVyVGVtcGxhdGUiOnsicmVzb3VyY2VzIjp7fSwiaW1hZ2VQdWxsUG9saWN5IjoiTmV2ZXIiLCJlbnYiOlt7Im5hbWUiOiJSVVNUX0xPRyIsInZhbHVlIjoiZGVidWcifV19LCJzY2FsZSI6eyJtaW4iOjF9LCJ1cGRhdGVTdHJhdGVneSI6eyJ0eXBlIjoiUm9sbGluZ1VwZGF0ZSIsInJvbGxpbmdVcGRhdGUiOnsibWF4VW5hdmFpbGFibGUiOiIyNSUifX19LHsibmFtZSI6InBsYW5uZXIiLCJ1ZGYiOnsiY29udGFpbmVyIjp7ImltYWdlIjoiYXNjaWk6MC4xIiwiYXJncyI6WyJwbGFubmVyIl0sInJlc291cmNlcyI6e30sImltYWdlUHVsbFBvbGljeSI6Ik5ldmVyIn0sImJ1aWx0aW4iOm51bGwsImdyb3VwQnkiOm51bGx9LCJjb250YWluZXJUZW1wbGF0ZSI6eyJyZXNvdXJjZXMiOnt9LCJpbWFnZVB1bGxQb2xpY3kiOiJOZXZlciJ9LCJzY2FsZSI6eyJtaW4iOjF9LCJ1cGRhdGVTdHJhdGVneSI6eyJ0eXBlIjoiUm9sbGluZ1VwZGF0ZSIsInJvbGxpbmdVcGRhdGUiOnsibWF4VW5hdmFpbGFibGUiOiIyNSUifX19LHsibmFtZSI6InRpZ2VyIiwidWRmIjp7ImNvbnRhaW5lciI6eyJpbWFnZSI6ImFzY2lpOjAuMSIsImFyZ3MiOlsidGlnZXIiXSwicmVzb3VyY2VzIjp7fSwiaW1hZ2VQdWxsUG9saWN5IjoiTmV2ZXIifSwiYnVpbHRpbiI6bnVsbCwiZ3JvdXBCeSI6bnVsbH0sImNvbnRhaW5lclRlbXBsYXRlIjp7InJlc291cmNlcyI6e30sImltYWdlUHVsbFBvbGljeSI6Ik5ldmVyIn0sInNjYWxlIjp7Im1pbiI6MX0sInVwZGF0ZVN0cmF0ZWd5Ijp7InR5cGUiOiJSb2xsaW5nVXBkYXRlIiwicm9sbGluZ1VwZGF0ZSI6eyJtYXhVbmF2YWlsYWJsZSI6IjI1JSJ9fX0seyJuYW1lIjoiZG9nIiwidWRmIjp7ImNvbnRhaW5lciI6eyJpbWFnZSI6ImFzY2lpOjAuMSIsImFyZ3MiOlsiZG9nIl0sInJlc291cmNlcyI6e30sImltYWdlUHVsbFBvbGljeSI6Ik5ldmVyIn0sImJ1aWx0aW4iOm51bGwsImdyb3VwQnkiOm51bGx9LCJjb250YWluZXJUZW1wbGF0ZSI6eyJyZXNvdXJjZXMiOnt9LCJpbWFnZVB1bGxQb2xpY3kiOiJOZXZlciJ9LCJzY2FsZSI6eyJtaW4iOjF9LCJ1cGRhdGVTdHJhdGVneSI6eyJ0eXBlIjoiUm9sbGluZ1VwZGF0ZSIsInJvbGxpbmdVcGRhdGUiOnsibWF4VW5hdmFpbGFibGUiOiIyNSUifX19LHsibmFtZSI6ImVsZXBoYW50IiwidWRmIjp7ImNvbnRhaW5lciI6eyJpbWFnZSI6ImFzY2lpOjAuMSIsImFyZ3MiOlsiZWxlcGhhbnQiXSwicmVzb3VyY2VzIjp7fSwiaW1hZ2VQdWxsUG9saWN5IjoiTmV2ZXIifSwiYnVpbHRpbiI6bnVsbCwiZ3JvdXBCeSI6bnVsbH0sImNvbnRhaW5lclRlbXBsYXRlIjp7InJlc291cmNlcyI6e30sImltYWdlUHVsbFBvbGljeSI6Ik5ldmVyIn0sInNjYWxlIjp7Im1pbiI6MX0sInVwZGF0ZVN0cmF0ZWd5Ijp7InR5cGUiOiJSb2xsaW5nVXBkYXRlIiwicm9sbGluZ1VwZGF0ZSI6eyJtYXhVbmF2YWlsYWJsZSI6IjI1JSJ9fX0seyJuYW1lIjoiYXNjaWlhcnQiLCJ1ZGYiOnsiY29udGFpbmVyIjp7ImltYWdlIjoiYXNjaWk6MC4xIiwiYXJncyI6WyJhc2NpaWFydCJdLCJyZXNvdXJjZXMiOnt9LCJpbWFnZVB1bGxQb2xpY3kiOiJOZXZlciJ9LCJidWlsdGluIjpudWxsLCJncm91cEJ5IjpudWxsfSwiY29udGFpbmVyVGVtcGxhdGUiOnsicmVzb3VyY2VzIjp7fSwiaW1hZ2VQdWxsUG9saWN5IjoiTmV2ZXIifSwic2NhbGUiOnsibWluIjoxfSwidXBkYXRlU3RyYXRlZ3kiOnsidHlwZSI6IlJvbGxpbmdVcGRhdGUiLCJyb2xsaW5nVXBkYXRlIjp7Im1heFVuYXZhaWxhYmxlIjoiMjUlIn19fSx7Im5hbWUiOiJzZXJ2ZS1zaW5rIiwic2luayI6eyJ1ZHNpbmsiOnsiY29udGFpbmVyIjp7ImltYWdlIjoic2VydmVzaW5rOjAuMSIsImVudiI6W3sibmFtZSI6Ik5VTUFGTE9XX0NBTExCQUNLX1VSTF9LRVkiLCJ2YWx1ZSI6IlgtTnVtYWZsb3ctQ2FsbGJhY2stVXJsIn0seyJuYW1lIjoiTlVNQUZMT1dfTVNHX0lEX0hFQURFUl9LRVkiLCJ2YWx1ZSI6IlgtTnVtYWZsb3ctSWQifV0sInJlc291cmNlcyI6e30sImltYWdlUHVsbFBvbGljeSI6Ik5ldmVyIn19LCJyZXRyeVN0cmF0ZWd5Ijp7fX0sImNvbnRhaW5lclRlbXBsYXRlIjp7InJlc291cmNlcyI6e30sImltYWdlUHVsbFBvbGljeSI6Ik5ldmVyIn0sInNjYWxlIjp7Im1pbiI6MX0sInVwZGF0ZVN0cmF0ZWd5Ijp7InR5cGUiOiJSb2xsaW5nVXBkYXRlIiwicm9sbGluZ1VwZGF0ZSI6eyJtYXhVbmF2YWlsYWJsZSI6IjI1JSJ9fX0seyJuYW1lIjoiZXJyb3Itc2luayIsInNpbmsiOnsidWRzaW5rIjp7ImNvbnRhaW5lciI6eyJpbWFnZSI6InNlcnZlc2luazowLjEiLCJlbnYiOlt7Im5hbWUiOiJOVU1BRkxPV19DQUxMQkFDS19VUkxfS0VZIiwidmFsdWUiOiJYLU51bWFmbG93LUNhbGxiYWNrLVVybCJ9LHsibmFtZSI6Ik5VTUFGTE9XX01TR19JRF9IRUFERVJfS0VZIiwidmFsdWUiOiJYLU51bWFmbG93LUlkIn1dLCJyZXNvdXJjZXMiOnt9LCJpbWFnZVB1bGxQb2xpY3kiOiJOZXZlciJ9fSwicmV0cnlTdHJhdGVneSI6e319LCJjb250YWluZXJUZW1wbGF0ZSI6eyJyZXNvdXJjZXMiOnt9LCJpbWFnZVB1bGxQb2xpY3kiOiJOZXZlciJ9LCJzY2FsZSI6eyJtaW4iOjF9LCJ1cGRhdGVTdHJhdGVneSI6eyJ0eXBlIjoiUm9sbGluZ1VwZGF0ZSIsInJvbGxpbmdVcGRhdGUiOnsibWF4VW5hdmFpbGFibGUiOiIyNSUifX19XSwiZWRnZXMiOlt7ImZyb20iOiJpbiIsInRvIjoicGxhbm5lciIsImNvbmRpdGlvbnMiOm51bGx9LHsiZnJvbSI6InBsYW5uZXIiLCJ0byI6ImFzY2lpYXJ0IiwiY29uZGl0aW9ucyI6eyJ0YWdzIjp7Im9wZXJhdG9yIjoib3IiLCJ2YWx1ZXMiOlsiYXNjaWlhcnQiXX19fSx7ImZyb20iOiJwbGFubmVyIiwidG8iOiJ0aWdlciIsImNvbmRpdGlvbnMiOnsidGFncyI6eyJvcGVyYXRvciI6Im9yIiwidmFsdWVzIjpbInRpZ2VyIl19fX0seyJmcm9tIjoicGxhbm5lciIsInRvIjoiZG9nIiwiY29uZGl0aW9ucyI6eyJ0YWdzIjp7Im9wZXJhdG9yIjoib3IiLCJ2YWx1ZXMiOlsiZG9nIl19fX0seyJmcm9tIjoicGxhbm5lciIsInRvIjoiZWxlcGhhbnQiLCJjb25kaXRpb25zIjp7InRhZ3MiOnsib3BlcmF0b3IiOiJvciIsInZhbHVlcyI6WyJlbGVwaGFudCJdfX19LHsiZnJvbSI6InRpZ2VyIiwidG8iOiJzZXJ2ZS1zaW5rIiwiY29uZGl0aW9ucyI6bnVsbH0seyJmcm9tIjoiZG9nIiwidG8iOiJzZXJ2ZS1zaW5rIiwiY29uZGl0aW9ucyI6bnVsbH0seyJmcm9tIjoiZWxlcGhhbnQiLCJ0byI6InNlcnZlLXNpbmsiLCJjb25kaXRpb25zIjpudWxsfSx7ImZyb20iOiJhc2NpaWFydCIsInRvIjoic2VydmUtc2luayIsImNvbmRpdGlvbnMiOm51bGx9LHsiZnJvbSI6InBsYW5uZXIiLCJ0byI6ImVycm9yLXNpbmsiLCJjb25kaXRpb25zIjp7InRhZ3MiOnsib3BlcmF0b3IiOiJvciIsInZhbHVlcyI6WyJlcnJvciJdfX19XSwibGlmZWN5Y2xlIjp7fSwid2F0ZXJtYXJrIjp7fX0="; diff --git a/rust/serving/src/config.rs b/rust/serving/src/config.rs index 16c2ee125..c485ad722 100644 --- a/rust/serving/src/config.rs +++ b/rust/serving/src/config.rs @@ -168,9 +168,8 @@ impl TryFrom> for Settings { #[cfg(test)] mod tests { - use crate::pipeline::{Edge, Vertex}; - use super::*; + use crate::pipeline::{Edge, Vertex}; #[test] fn test_default_config() { diff --git a/rust/serving/src/lib.rs b/rust/serving/src/lib.rs index bdc3aeab9..7fe953616 100644 --- a/rust/serving/src/lib.rs +++ b/rust/serving/src/lib.rs @@ -1,13 +1,13 @@ use std::net::SocketAddr; use std::sync::Arc; -use crate::app::callback::state::State as CallbackState; use app::callback::store::Store; use axum_server::tls_rustls::RustlsConfig; use tokio::sync::mpsc; use tracing::info; pub use self::error::{Error, Result}; +use crate::app::callback::state::State as CallbackState; use crate::app::start_main_server; use crate::config::generate_certs; use crate::metrics::start_https_metrics_server; @@ -23,9 +23,10 @@ mod metrics; mod pipeline; pub mod source; -use crate::source::MessageWrapper; pub use source::{Message, ServingSource}; +use crate::source::MessageWrapper; + #[derive(Clone)] pub(crate) struct AppState { pub(crate) message: mpsc::Sender, diff --git a/rust/serving/src/source.rs b/rust/serving/src/source.rs index d03817967..0efce5199 100644 --- a/rust/serving/src/source.rs +++ b/rust/serving/src/source.rs @@ -227,9 +227,8 @@ impl ServingSource { mod tests { use std::{sync::Arc, time::Duration}; - use crate::Settings; - use super::ServingSource; + use crate::Settings; type Result = std::result::Result>; #[tokio::test] From 376a6023ba735e1d483862cf1a77b86009c59a1d Mon Sep 17 00:00:00 2001 From: Vedant Gupta <49195734+veds-g@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:03:44 +0530 Subject: [PATCH 8/9] feat: logs enhancements (#2320) Signed-off-by: veds-g --- .../Pods/partials/PodDetails/index.tsx | 3 +- .../PodDetails/partials/PodLogs/index.tsx | 340 +++++++++++++++--- ui/src/types/declarations/pods.d.ts | 1 + 3 files changed, 294 insertions(+), 50 deletions(-) diff --git a/ui/src/components/pages/Pipeline/partials/Graph/partials/NodeInfo/partials/Pods/partials/PodDetails/index.tsx b/ui/src/components/pages/Pipeline/partials/Graph/partials/NodeInfo/partials/Pods/partials/PodDetails/index.tsx index b29cd1836..5732eb252 100644 --- a/ui/src/components/pages/Pipeline/partials/Graph/partials/NodeInfo/partials/Pods/partials/PodDetails/index.tsx +++ b/ui/src/components/pages/Pipeline/partials/Graph/partials/NodeInfo/partials/Pods/partials/PodDetails/index.tsx @@ -26,7 +26,7 @@ export function PodDetail({ type, containerName, pod, - vertexId + vertexId, }: PodDetailProps) { if (!pod) return null; @@ -90,6 +90,7 @@ export function PodDetail({ namespaceId={namespaceId} podName={pod.name} containerName={containerName} + type={type} /> diff --git a/ui/src/components/pages/Pipeline/partials/Graph/partials/NodeInfo/partials/Pods/partials/PodDetails/partials/PodLogs/index.tsx b/ui/src/components/pages/Pipeline/partials/Graph/partials/NodeInfo/partials/Pods/partials/PodDetails/partials/PodLogs/index.tsx index 565775edd..e3ec43947 100644 --- a/ui/src/components/pages/Pipeline/partials/Graph/partials/NodeInfo/partials/Pods/partials/PodDetails/partials/PodLogs/index.tsx +++ b/ui/src/components/pages/Pipeline/partials/Graph/partials/NodeInfo/partials/Pods/partials/PodDetails/partials/PodLogs/index.tsx @@ -8,7 +8,10 @@ import { useState, } from "react"; import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; import Paper from "@mui/material/Paper"; +import Select from "@mui/material/Select"; +import MenuItem from "@mui/material/MenuItem"; import InputBase from "@mui/material/InputBase"; import IconButton from "@mui/material/IconButton"; import ClearIcon from "@mui/icons-material/Clear"; @@ -18,6 +21,9 @@ import ArrowUpward from "@mui/icons-material/ArrowUpward"; import ArrowDownward from "@mui/icons-material/ArrowDownward"; import LightMode from "@mui/icons-material/LightMode"; import DarkMode from "@mui/icons-material/DarkMode"; +import Download from "@mui/icons-material/Download"; +import WrapTextIcon from "@mui/icons-material/WrapText"; +import { ClockIcon } from "@mui/x-date-pickers"; import Tooltip from "@mui/material/Tooltip"; import FormControlLabel from "@mui/material/FormControlLabel"; import Checkbox from "@mui/material/Checkbox"; @@ -30,57 +36,112 @@ import { AppContextProps } from "../../../../../../../../../../../../../types/de import { AppContext } from "../../../../../../../../../../../../../App"; import "./style.css"; -import Typography from "@mui/material/Typography"; const MAX_LOGS = 1000; +// const LOGS_LEVEL_ERROR = "ERROR"; +// const LOGS_LEVEL_DEBUG = "DEBUG"; +// const LOGS_LEVEL_WARN = "WARN"; -const parsePodLogs = (value: string): string[] => { +const parsePodLogs = ( + value: string, + enableTimestamp: boolean, + levelFilter: string, + type: string +): string[] => { const rawLogs = value.split("\n").filter((s) => s.length); return rawLogs.map((raw: string) => { try { - const obj = JSON.parse(raw); - let msg = ``; - if (obj?.ts) { - const date = obj.ts.split(/[-T:.Z]/); - const ds = - date[0] + - "/" + - date[1] + - "/" + - date[2] + - " " + - date[3] + - ":" + - date[4] + - ":" + - date[5]; - msg = `${msg}${ds} | `; - } - if (obj?.level) { - msg = `${msg}${obj.level.toUpperCase()} | `; + if (type === "monoVertex") { + const msg = raw; + if (levelFilter !== "all" && !msg.toLowerCase().includes(levelFilter)) { + return ""; + } + if (!enableTimestamp) { + // remove ISO 8601 timestamp from beginning of log if it exists + const date = msg.match( + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z/ + ); + if (date) { + return msg.substring(28); + } + } + return msg; + } else { + const obj = JSON.parse(raw); + let msg = ``; + if (enableTimestamp && obj?.ts) { + const date = obj.ts.split(/[-T:.Z]/); + const ds = + date[0] + + "/" + + date[1] + + "/" + + date[2] + + " " + + date[3] + + ":" + + date[4] + + ":" + + date[5]; + msg = `${msg}${ds} | `; + } + if (obj?.level) { + msg = `${msg}${obj.level.toUpperCase()} | `; + if (levelFilter !== "all" && obj.level !== levelFilter) { + return ""; + } + } + msg = `${msg}${raw}`; + return msg; } - msg = `${msg}${raw}`; - return msg; } catch (e) { return raw; } }); }; -const logColor = (log: string, colorMode: string): string => { - if (log.startsWith("ERROR", 22)) { - return "#B80000"; - } - if (log.startsWith("WARN", 22)) { - return "#FFAD00"; - } - if (log.startsWith("DEBUG", 22)) { - return "#81b8ef"; - } - return colorMode === "light" ? "black" : "white"; -}; +// const logColor = ( +// log: string, +// colorMode: string, +// enableTimestamp: boolean, +// type: string +// ): string => { +// const logLevelColors: { [key: string]: string } = { +// ERROR: "#B80000", +// WARN: "#FFAD00", +// DEBUG: "#81b8ef", +// }; +// +// let startIndex = 0; +// if (enableTimestamp) { +// if (type === "monoVertex") { +// if (log?.includes(LOGS_LEVEL_ERROR) || log?.includes(LOGS_LEVEL_DEBUG)) +// startIndex = 28; +// else startIndex = 29; +// } else { +// startIndex = 22; +// } +// } else { +// if (type === "monoVertex") { +// if (log?.includes(LOGS_LEVEL_WARN)) startIndex = 1; +// } +// } +// +// for (const level in logLevelColors) { +// if (log.startsWith(level, startIndex)) { +// return logLevelColors[level]; +// } +// } +// +// return colorMode === "light" ? "black" : "white"; +// }; -export function PodLogs({ namespaceId, podName, containerName }: PodLogsProps) { +export function PodLogs({ + namespaceId, + podName, + containerName, + type, +}: PodLogsProps) { const [logs, setLogs] = useState([]); const [previousLogs, setPreviousLogs] = useState([]); const [filteredLogs, setFilteredLogs] = useState([]); @@ -90,9 +151,12 @@ export function PodLogs({ namespaceId, podName, containerName }: PodLogsProps) { >(); const [search, setSearch] = useState(""); const [negateSearch, setNegateSearch] = useState(false); + const [wrapLines, setWrapLines] = useState(false); const [paused, setPaused] = useState(false); const [colorMode, setColorMode] = useState("light"); const [logsOrder, setLogsOrder] = useState("desc"); + const [enableTimestamp, setEnableTimestamp] = useState(true); + const [levelFilter, setLevelFilter] = useState("all"); const [showPreviousLogs, setShowPreviousLogs] = useState(false); const { host } = useContext(AppContext); @@ -138,7 +202,12 @@ export function PodLogs({ namespaceId, podName, containerName }: PodLogsProps) { } if (value) { setLogs((logs) => { - const latestLogs = parsePodLogs(value); + const latestLogs = parsePodLogs( + value, + enableTimestamp, + levelFilter, + type + )?.filter((logs) => logs !== ""); let updated = [...logs, ...latestLogs]; if (updated.length > MAX_LOGS) { updated = updated.slice(updated.length - MAX_LOGS); @@ -151,7 +220,16 @@ export function PodLogs({ namespaceId, podName, containerName }: PodLogsProps) { } }) .catch(console.error); - }, [namespaceId, podName, containerName, reader, paused, host]); + }, [ + namespaceId, + podName, + containerName, + reader, + paused, + host, + enableTimestamp, + levelFilter, + ]); useEffect(() => { if (showPreviousLogs) { @@ -170,7 +248,12 @@ export function PodLogs({ namespaceId, podName, containerName }: PodLogsProps) { } if (value) { setPreviousLogs((prevLogs) => { - const latestLogs = parsePodLogs(value); + const latestLogs = parsePodLogs( + value, + enableTimestamp, + levelFilter, + type + )?.filter((logs) => logs !== ""); let updated = [...prevLogs, ...latestLogs]; if (updated.length > MAX_LOGS) { updated = updated.slice(updated.length - MAX_LOGS); @@ -187,7 +270,15 @@ export function PodLogs({ namespaceId, podName, containerName }: PodLogsProps) { // Clear previous logs when the checkbox is unchecked setPreviousLogs([]); } - }, [showPreviousLogs, namespaceId, podName, containerName, host]); + }, [ + showPreviousLogs, + namespaceId, + podName, + containerName, + host, + enableTimestamp, + levelFilter, + ]); useEffect(() => { if (!search) { @@ -224,6 +315,10 @@ export function PodLogs({ namespaceId, podName, containerName }: PodLogsProps) { [] ); + const handleWrapLines = useCallback(() => { + setWrapLines((prev) => !prev); + }, []); + const handlePause = useCallback(() => { setPaused(!paused); if (!paused && reader) { @@ -240,7 +335,49 @@ export function PodLogs({ namespaceId, podName, containerName }: PodLogsProps) { setLogsOrder(logsOrder === "asc" ? "desc" : "asc"); }, [logsOrder]); - const logsBtnStyle = { height: "2.4rem", width: "2.4rem" }; + const handleLogsDownload = useCallback(() => { + const blob = new Blob([logs.join("\n")], { + type: "text/plain;charset=utf-8", + }); + + const url = URL.createObjectURL(blob); + + const a = document.createElement("a"); + a.href = url; + a.download = `${podName}-${containerName}-logs.txt`; + + document.body.appendChild(a); + + a.click(); + + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, [logs]); + + const handleTimestamps = useCallback(() => { + setEnableTimestamp((prev) => !prev); + if (reader) { + reader.cancel(); + setReader(undefined); + } + }, [reader]); + + const handleLevelChange = useCallback( + (e) => { + setLevelFilter(e.target.value); + if (reader) { + reader.cancel(); + setReader(undefined); + } + }, + [reader] + ); + + const logsBtnStyle = { + height: "2.4rem", + width: "2.4rem", + color: "rgba(0, 0, 0, 0.54)", + }; return ( @@ -283,6 +420,25 @@ export function PodLogs({ namespaceId, podName, containerName }: PodLogsProps) { Negate search } /> + + {wrapLines ? "Unwrap Lines" : "Wrap Lines"} + + } + placement={"top"} + arrow + > + + + + @@ -334,6 +490,66 @@ export function PodLogs({ namespaceId, podName, containerName }: PodLogsProps) { )} + Download logs} + placement={"top"} + arrow + > + + + + + + {enableTimestamp ? "Remove Timestamps" : "Add Timestamps"} + + } + placement={"top"} + arrow + > + + + + + Date: Wed, 15 Jan 2025 13:08:55 +0530 Subject: [PATCH 9/9] feat: Enhance Serving To Support Flatmap Operations (#2324) Signed-off-by: Yashash H L Signed-off-by: Vigith Maurice Co-authored-by: Vigith Maurice --- rust/numaflow-core/src/metrics.rs | 1 - rust/serving/src/app/callback.rs | 33 +- rust/serving/src/app/callback/state.rs | 68 +- rust/serving/src/app/callback/store.rs | 12 +- .../src/app/callback/store/memstore.rs | 16 +- .../src/app/callback/store/redisstore.rs | 20 +- rust/serving/src/app/jetstream_proxy.rs | 30 +- rust/serving/src/app/tracker.rs | 880 +++++++++++++----- 8 files changed, 752 insertions(+), 308 deletions(-) diff --git a/rust/numaflow-core/src/metrics.rs b/rust/numaflow-core/src/metrics.rs index d63b82de7..4549ade9f 100644 --- a/rust/numaflow-core/src/metrics.rs +++ b/rust/numaflow-core/src/metrics.rs @@ -718,7 +718,6 @@ struct TimestampedPending { #[derive(Clone)] pub(crate) enum LagReader { Source(Source), - // TODO: Arc<[T]> ISB(Vec), // multiple partitions } diff --git a/rust/serving/src/app/callback.rs b/rust/serving/src/app/callback.rs index b4d43868e..d5708a4e6 100644 --- a/rust/serving/src/app/callback.rs +++ b/rust/serving/src/app/callback.rs @@ -12,12 +12,23 @@ use state::State as CallbackState; /// store for storing the state pub(crate) mod store; -#[derive(Debug, Serialize, Deserialize, Clone)] -pub(crate) struct CallbackRequest { +/// As message passes through each component (map, transformer, sink, etc.). it emits a beacon via callback +/// to inform that message has been processed by this component. +#[derive(Debug, Serialize, Deserialize)] +pub(crate) struct Callback { pub(crate) id: String, pub(crate) vertex: String, pub(crate) cb_time: u64, pub(crate) from_vertex: String, + /// Due to flat-map operation, we can have 0 or more responses. + pub(crate) responses: Vec, +} + +/// It contains details about the `To` vertex via tags (conditional forwarding). +#[derive(Debug, Serialize, Deserialize)] +pub(crate) struct Response { + /// If tags is None, the message is forwarded to all vertices, if len(Vec) == 0, it means that + /// the message has been dropped. pub(crate) tags: Option>, } @@ -68,7 +79,7 @@ async fn callback_save( async fn callback( State(app_state): State>, - Json(payload): Json>, + Json(payload): Json>, ) -> Result<(), ApiError> { app_state .callback_state @@ -107,12 +118,12 @@ mod tests { let state = CallbackState::new(msg_graph, store).await.unwrap(); let app = callback_handler("ID".to_owned(), state); - let payload = vec![CallbackRequest { + let payload = vec![Callback { id: "test_id".to_string(), vertex: "in".to_string(), cb_time: 12345, from_vertex: "in".to_string(), - tags: None, + responses: vec![Response { tags: None }], }]; let res = Request::builder() @@ -143,26 +154,26 @@ mod tests { let app = callback_handler("ID".to_owned(), state); let payload = vec![ - CallbackRequest { + Callback { id: "test_id".to_string(), vertex: "in".to_string(), cb_time: 12345, from_vertex: "in".to_string(), - tags: None, + responses: vec![Response { tags: None }], }, - CallbackRequest { + Callback { id: "test_id".to_string(), vertex: "cat".to_string(), cb_time: 12345, from_vertex: "in".to_string(), - tags: None, + responses: vec![Response { tags: None }], }, - CallbackRequest { + Callback { id: "test_id".to_string(), vertex: "out".to_string(), cb_time: 12345, from_vertex: "cat".to_string(), - tags: None, + responses: vec![Response { tags: None }], }, ]; diff --git a/rust/serving/src/app/callback/state.rs b/rust/serving/src/app/callback/state.rs index 293478ead..419a40f60 100644 --- a/rust/serving/src/app/callback/state.rs +++ b/rust/serving/src/app/callback/state.rs @@ -6,7 +6,7 @@ use std::{ use tokio::sync::oneshot; use super::store::Store; -use crate::app::callback::{store::PayloadToSave, CallbackRequest}; +use crate::app::callback::{store::PayloadToSave, Callback}; use crate::app::tracker::MessageGraph; use crate::Error; @@ -14,7 +14,7 @@ struct RequestState { // Channel to notify when all callbacks for a message is received tx: oneshot::Sender>, // CallbackRequest is immutable, while vtx_visited can grow. - vtx_visited: Vec>, + vtx_visited: Vec>, } #[derive(Clone)] @@ -81,15 +81,14 @@ where /// insert_callback_requests is used to insert the callback requests. pub(crate) async fn insert_callback_requests( &mut self, - cb_requests: Vec, + cb_requests: Vec, ) -> Result<(), Error> { /* TODO: should we consider batching the requests and then processing them? that way algorithm can be invoked only once for a batch of requests instead of invoking it for each request. */ - let cb_requests: Vec> = - cb_requests.into_iter().map(Arc::new).collect(); + let cb_requests: Vec> = cb_requests.into_iter().map(Arc::new).collect(); let redis_payloads: Vec = cb_requests .iter() .cloned() @@ -153,23 +152,18 @@ where id: &str, ) -> Result { // If the id is not found in the in-memory store, fetch from Redis - let callbacks: Vec> = - match self.retrieve_callbacks_from_storage(id).await { - Ok(callbacks) => callbacks, - Err(e) => { - return Err(e); - } - }; + let callbacks: Vec> = match self.retrieve_callbacks_from_storage(id).await { + Ok(callbacks) => callbacks, + Err(e) => { + return Err(e); + } + }; // check if the sub graph can be generated self.get_subgraph(id.to_string(), callbacks) } // Generate subgraph from the given callbacks - fn get_subgraph( - &self, - id: String, - callbacks: Vec>, - ) -> Result { + fn get_subgraph(&self, id: String, callbacks: Vec>) -> Result { match self .msg_graph_generator .generate_subgraph_from_callbacks(id, callbacks) @@ -204,7 +198,7 @@ where // Get the Callback value for the given ID // TODO: Generate json serialized data here itself to avoid cloning. - fn get_callbacks_from_memory(&self, id: &str) -> Option>> { + fn get_callbacks_from_memory(&self, id: &str) -> Option>> { let guard = self.callbacks.lock().expect("Getting lock on State"); guard.get(id).map(|state| state.vtx_visited.clone()) } @@ -213,9 +207,9 @@ where async fn retrieve_callbacks_from_storage( &mut self, id: &str, - ) -> Result>, Error> { + ) -> Result>, Error> { // If the id is not found in the in-memory store, fetch from Redis - let callbacks: Vec> = match self.store.retrieve_callbacks(id).await { + let callbacks: Vec> = match self.store.retrieve_callbacks(id).await { Ok(response) => response.into_iter().collect(), Err(e) => { return Err(e); @@ -232,11 +226,11 @@ where #[cfg(test)] mod tests { - use axum::body::Bytes; - use super::*; use crate::app::callback::store::memstore::InMemoryStore; + use crate::app::callback::Response; use crate::pipeline::PipelineDCG; + use axum::body::Bytes; const PIPELINE_SPEC_ENCODED: &str = "eyJ2ZXJ0aWNlcyI6W3sibmFtZSI6ImluIiwic291cmNlIjp7InNlcnZpbmciOnsiYXV0aCI6bnVsbCwic2VydmljZSI6dHJ1ZSwibXNnSURIZWFkZXJLZXkiOiJYLU51bWFmbG93LUlkIiwic3RvcmUiOnsidXJsIjoicmVkaXM6Ly9yZWRpczo2Mzc5In19fSwiY29udGFpbmVyVGVtcGxhdGUiOnsicmVzb3VyY2VzIjp7fSwiaW1hZ2VQdWxsUG9saWN5IjoiTmV2ZXIiLCJlbnYiOlt7Im5hbWUiOiJSVVNUX0xPRyIsInZhbHVlIjoiZGVidWcifV19LCJzY2FsZSI6eyJtaW4iOjF9LCJ1cGRhdGVTdHJhdGVneSI6eyJ0eXBlIjoiUm9sbGluZ1VwZGF0ZSIsInJvbGxpbmdVcGRhdGUiOnsibWF4VW5hdmFpbGFibGUiOiIyNSUifX19LHsibmFtZSI6InBsYW5uZXIiLCJ1ZGYiOnsiY29udGFpbmVyIjp7ImltYWdlIjoiYXNjaWk6MC4xIiwiYXJncyI6WyJwbGFubmVyIl0sInJlc291cmNlcyI6e30sImltYWdlUHVsbFBvbGljeSI6Ik5ldmVyIn0sImJ1aWx0aW4iOm51bGwsImdyb3VwQnkiOm51bGx9LCJjb250YWluZXJUZW1wbGF0ZSI6eyJyZXNvdXJjZXMiOnt9LCJpbWFnZVB1bGxQb2xpY3kiOiJOZXZlciJ9LCJzY2FsZSI6eyJtaW4iOjF9LCJ1cGRhdGVTdHJhdGVneSI6eyJ0eXBlIjoiUm9sbGluZ1VwZGF0ZSIsInJvbGxpbmdVcGRhdGUiOnsibWF4VW5hdmFpbGFibGUiOiIyNSUifX19LHsibmFtZSI6InRpZ2VyIiwidWRmIjp7ImNvbnRhaW5lciI6eyJpbWFnZSI6ImFzY2lpOjAuMSIsImFyZ3MiOlsidGlnZXIiXSwicmVzb3VyY2VzIjp7fSwiaW1hZ2VQdWxsUG9saWN5IjoiTmV2ZXIifSwiYnVpbHRpbiI6bnVsbCwiZ3JvdXBCeSI6bnVsbH0sImNvbnRhaW5lclRlbXBsYXRlIjp7InJlc291cmNlcyI6e30sImltYWdlUHVsbFBvbGljeSI6Ik5ldmVyIn0sInNjYWxlIjp7Im1pbiI6MX0sInVwZGF0ZVN0cmF0ZWd5Ijp7InR5cGUiOiJSb2xsaW5nVXBkYXRlIiwicm9sbGluZ1VwZGF0ZSI6eyJtYXhVbmF2YWlsYWJsZSI6IjI1JSJ9fX0seyJuYW1lIjoiZG9nIiwidWRmIjp7ImNvbnRhaW5lciI6eyJpbWFnZSI6ImFzY2lpOjAuMSIsImFyZ3MiOlsiZG9nIl0sInJlc291cmNlcyI6e30sImltYWdlUHVsbFBvbGljeSI6Ik5ldmVyIn0sImJ1aWx0aW4iOm51bGwsImdyb3VwQnkiOm51bGx9LCJjb250YWluZXJUZW1wbGF0ZSI6eyJyZXNvdXJjZXMiOnt9LCJpbWFnZVB1bGxQb2xpY3kiOiJOZXZlciJ9LCJzY2FsZSI6eyJtaW4iOjF9LCJ1cGRhdGVTdHJhdGVneSI6eyJ0eXBlIjoiUm9sbGluZ1VwZGF0ZSIsInJvbGxpbmdVcGRhdGUiOnsibWF4VW5hdmFpbGFibGUiOiIyNSUifX19LHsibmFtZSI6ImVsZXBoYW50IiwidWRmIjp7ImNvbnRhaW5lciI6eyJpbWFnZSI6ImFzY2lpOjAuMSIsImFyZ3MiOlsiZWxlcGhhbnQiXSwicmVzb3VyY2VzIjp7fSwiaW1hZ2VQdWxsUG9saWN5IjoiTmV2ZXIifSwiYnVpbHRpbiI6bnVsbCwiZ3JvdXBCeSI6bnVsbH0sImNvbnRhaW5lclRlbXBsYXRlIjp7InJlc291cmNlcyI6e30sImltYWdlUHVsbFBvbGljeSI6Ik5ldmVyIn0sInNjYWxlIjp7Im1pbiI6MX0sInVwZGF0ZVN0cmF0ZWd5Ijp7InR5cGUiOiJSb2xsaW5nVXBkYXRlIiwicm9sbGluZ1VwZGF0ZSI6eyJtYXhVbmF2YWlsYWJsZSI6IjI1JSJ9fX0seyJuYW1lIjoiYXNjaWlhcnQiLCJ1ZGYiOnsiY29udGFpbmVyIjp7ImltYWdlIjoiYXNjaWk6MC4xIiwiYXJncyI6WyJhc2NpaWFydCJdLCJyZXNvdXJjZXMiOnt9LCJpbWFnZVB1bGxQb2xpY3kiOiJOZXZlciJ9LCJidWlsdGluIjpudWxsLCJncm91cEJ5IjpudWxsfSwiY29udGFpbmVyVGVtcGxhdGUiOnsicmVzb3VyY2VzIjp7fSwiaW1hZ2VQdWxsUG9saWN5IjoiTmV2ZXIifSwic2NhbGUiOnsibWluIjoxfSwidXBkYXRlU3RyYXRlZ3kiOnsidHlwZSI6IlJvbGxpbmdVcGRhdGUiLCJyb2xsaW5nVXBkYXRlIjp7Im1heFVuYXZhaWxhYmxlIjoiMjUlIn19fSx7Im5hbWUiOiJzZXJ2ZS1zaW5rIiwic2luayI6eyJ1ZHNpbmsiOnsiY29udGFpbmVyIjp7ImltYWdlIjoic2VydmVzaW5rOjAuMSIsImVudiI6W3sibmFtZSI6Ik5VTUFGTE9XX0NBTExCQUNLX1VSTF9LRVkiLCJ2YWx1ZSI6IlgtTnVtYWZsb3ctQ2FsbGJhY2stVXJsIn0seyJuYW1lIjoiTlVNQUZMT1dfTVNHX0lEX0hFQURFUl9LRVkiLCJ2YWx1ZSI6IlgtTnVtYWZsb3ctSWQifV0sInJlc291cmNlcyI6e30sImltYWdlUHVsbFBvbGljeSI6Ik5ldmVyIn19LCJyZXRyeVN0cmF0ZWd5Ijp7fX0sImNvbnRhaW5lclRlbXBsYXRlIjp7InJlc291cmNlcyI6e30sImltYWdlUHVsbFBvbGljeSI6Ik5ldmVyIn0sInNjYWxlIjp7Im1pbiI6MX0sInVwZGF0ZVN0cmF0ZWd5Ijp7InR5cGUiOiJSb2xsaW5nVXBkYXRlIiwicm9sbGluZ1VwZGF0ZSI6eyJtYXhVbmF2YWlsYWJsZSI6IjI1JSJ9fX0seyJuYW1lIjoiZXJyb3Itc2luayIsInNpbmsiOnsidWRzaW5rIjp7ImNvbnRhaW5lciI6eyJpbWFnZSI6InNlcnZlc2luazowLjEiLCJlbnYiOlt7Im5hbWUiOiJOVU1BRkxPV19DQUxMQkFDS19VUkxfS0VZIiwidmFsdWUiOiJYLU51bWFmbG93LUNhbGxiYWNrLVVybCJ9LHsibmFtZSI6Ik5VTUFGTE9XX01TR19JRF9IRUFERVJfS0VZIiwidmFsdWUiOiJYLU51bWFmbG93LUlkIn1dLCJyZXNvdXJjZXMiOnt9LCJpbWFnZVB1bGxQb2xpY3kiOiJOZXZlciJ9fSwicmV0cnlTdHJhdGVneSI6e319LCJjb250YWluZXJUZW1wbGF0ZSI6eyJyZXNvdXJjZXMiOnt9LCJpbWFnZVB1bGxQb2xpY3kiOiJOZXZlciJ9LCJzY2FsZSI6eyJtaW4iOjF9LCJ1cGRhdGVTdHJhdGVneSI6eyJ0eXBlIjoiUm9sbGluZ1VwZGF0ZSIsInJvbGxpbmdVcGRhdGUiOnsibWF4VW5hdmFpbGFibGUiOiIyNSUifX19XSwiZWRnZXMiOlt7ImZyb20iOiJpbiIsInRvIjoicGxhbm5lciIsImNvbmRpdGlvbnMiOm51bGx9LHsiZnJvbSI6InBsYW5uZXIiLCJ0byI6ImFzY2lpYXJ0IiwiY29uZGl0aW9ucyI6eyJ0YWdzIjp7Im9wZXJhdG9yIjoib3IiLCJ2YWx1ZXMiOlsiYXNjaWlhcnQiXX19fSx7ImZyb20iOiJwbGFubmVyIiwidG8iOiJ0aWdlciIsImNvbmRpdGlvbnMiOnsidGFncyI6eyJvcGVyYXRvciI6Im9yIiwidmFsdWVzIjpbInRpZ2VyIl19fX0seyJmcm9tIjoicGxhbm5lciIsInRvIjoiZG9nIiwiY29uZGl0aW9ucyI6eyJ0YWdzIjp7Im9wZXJhdG9yIjoib3IiLCJ2YWx1ZXMiOlsiZG9nIl19fX0seyJmcm9tIjoicGxhbm5lciIsInRvIjoiZWxlcGhhbnQiLCJjb25kaXRpb25zIjp7InRhZ3MiOnsib3BlcmF0b3IiOiJvciIsInZhbHVlcyI6WyJlbGVwaGFudCJdfX19LHsiZnJvbSI6InRpZ2VyIiwidG8iOiJzZXJ2ZS1zaW5rIiwiY29uZGl0aW9ucyI6bnVsbH0seyJmcm9tIjoiZG9nIiwidG8iOiJzZXJ2ZS1zaW5rIiwiY29uZGl0aW9ucyI6bnVsbH0seyJmcm9tIjoiZWxlcGhhbnQiLCJ0byI6InNlcnZlLXNpbmsiLCJjb25kaXRpb25zIjpudWxsfSx7ImZyb20iOiJhc2NpaWFydCIsInRvIjoic2VydmUtc2luayIsImNvbmRpdGlvbnMiOm51bGx9LHsiZnJvbSI6InBsYW5uZXIiLCJ0byI6ImVycm9yLXNpbmsiLCJjb25kaXRpb25zIjp7InRhZ3MiOnsib3BlcmF0b3IiOiJvciIsInZhbHVlcyI6WyJlcnJvciJdfX19XSwibGlmZWN5Y2xlIjp7fSwid2F0ZXJtYXJrIjp7fX0="; @@ -271,47 +265,49 @@ mod tests { // Test insert_callback_requests let cbs = vec![ - CallbackRequest { + Callback { id: id.clone(), vertex: "in".to_string(), cb_time: 12345, from_vertex: "in".to_string(), - tags: None, + responses: vec![Response { tags: None }], }, - CallbackRequest { + Callback { id: id.clone(), vertex: "planner".to_string(), cb_time: 12345, from_vertex: "in".to_string(), - tags: Some(vec!["tiger".to_owned(), "asciiart".to_owned()]), + responses: vec![Response { + tags: Some(vec!["tiger".to_owned(), "asciiart".to_owned()]), + }], }, - CallbackRequest { + Callback { id: id.clone(), vertex: "tiger".to_string(), cb_time: 12345, from_vertex: "planner".to_string(), - tags: None, + responses: vec![Response { tags: None }], }, - CallbackRequest { + Callback { id: id.clone(), vertex: "asciiart".to_string(), cb_time: 12345, from_vertex: "planner".to_string(), - tags: None, + responses: vec![Response { tags: None }], }, - CallbackRequest { + Callback { id: id.clone(), vertex: "serve-sink".to_string(), cb_time: 12345, from_vertex: "tiger".to_string(), - tags: None, + responses: vec![Response { tags: None }], }, - CallbackRequest { + Callback { id: id.clone(), vertex: "serve-sink".to_string(), cb_time: 12345, from_vertex: "asciiart".to_string(), - tags: None, + responses: vec![Response { tags: None }], }, ]; state.insert_callback_requests(cbs).await.unwrap(); @@ -345,12 +341,12 @@ mod tests { let store = InMemoryStore::new(); let mut state = State::new(msg_graph, store).await.unwrap(); - let cbs = vec![CallbackRequest { + let cbs = vec![Callback { id: "nonexistent_id".to_string(), vertex: "in".to_string(), cb_time: 12345, from_vertex: "in".to_string(), - tags: None, + responses: vec![Response { tags: None }], }]; // Try to insert callback requests for an ID that hasn't been registered diff --git a/rust/serving/src/app/callback/store.rs b/rust/serving/src/app/callback/store.rs index af5f3c436..4165a9340 100644 --- a/rust/serving/src/app/callback/store.rs +++ b/rust/serving/src/app/callback/store.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::app::callback::CallbackRequest; +use crate::app::callback::Callback; // in-memory store pub(crate) mod memstore; @@ -9,10 +9,7 @@ pub(crate) mod redisstore; pub(crate) enum PayloadToSave { /// Callback as sent by Numaflow to track the progression - Callback { - key: String, - value: Arc, - }, + Callback { key: String, value: Arc }, /// Data sent by the Numaflow pipeline which is to be delivered as the response DatumFromPipeline { key: String, @@ -26,10 +23,7 @@ pub(crate) enum PayloadToSave { pub(crate) trait LocalStore { async fn save(&mut self, messages: Vec) -> crate::Result<()>; /// retrieve the callback payloads - async fn retrieve_callbacks( - &mut self, - id: &str, - ) -> Result>, crate::Error>; + async fn retrieve_callbacks(&mut self, id: &str) -> Result>, crate::Error>; async fn retrieve_datum(&mut self, id: &str) -> Result>, crate::Error>; async fn ready(&mut self) -> bool; } diff --git a/rust/serving/src/app/callback/store/memstore.rs b/rust/serving/src/app/callback/store/memstore.rs index a9cbaea31..4c5c36666 100644 --- a/rust/serving/src/app/callback/store/memstore.rs +++ b/rust/serving/src/app/callback/store/memstore.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::sync::Arc; use super::PayloadToSave; -use crate::app::callback::CallbackRequest; +use crate::app::callback::Callback; use crate::consts::SAVED; use crate::Error; @@ -55,14 +55,14 @@ impl super::Store for InMemoryStore { /// Retrieves callbacks for a given id from the `HashMap`. /// Each callback is deserialized from bytes into a `CallbackRequest`. - async fn retrieve_callbacks(&mut self, id: &str) -> Result>, Error> { + async fn retrieve_callbacks(&mut self, id: &str) -> Result>, Error> { let data = self.data.lock().unwrap(); match data.get(id) { Some(result) => { let messages: Result, _> = result .iter() .map(|msg| { - let cbr: CallbackRequest = serde_json::from_slice(msg).map_err(|_| { + let cbr: Callback = serde_json::from_slice(msg).map_err(|_| { Error::StoreRead( "Failed to parse CallbackRequest from bytes".to_string(), ) @@ -98,18 +98,18 @@ mod tests { use super::*; use crate::app::callback::store::{PayloadToSave, Store}; - use crate::app::callback::CallbackRequest; + use crate::app::callback::{Callback, Response}; #[tokio::test] async fn test_save_and_retrieve_callbacks() { let mut store = InMemoryStore::new(); let key = "test_key".to_string(); - let value = Arc::new(CallbackRequest { + let value = Arc::new(Callback { id: "test_id".to_string(), vertex: "in".to_string(), cb_time: 12345, from_vertex: "in".to_string(), - tags: None, + responses: vec![Response { tags: None }], }); // Save a callback @@ -179,12 +179,12 @@ mod tests { #[tokio::test] async fn test_save_invalid_callback() { let mut store = InMemoryStore::new(); - let value = Arc::new(CallbackRequest { + let value = Arc::new(Callback { id: "test_id".to_string(), vertex: "in".to_string(), cb_time: 12345, from_vertex: "in".to_string(), - tags: None, + responses: vec![Response { tags: None }], }); // Try to save a callback with an invalid key diff --git a/rust/serving/src/app/callback/store/redisstore.rs b/rust/serving/src/app/callback/store/redisstore.rs index 4439e7ce8..d4fe501cd 100644 --- a/rust/serving/src/app/callback/store/redisstore.rs +++ b/rust/serving/src/app/callback/store/redisstore.rs @@ -7,7 +7,7 @@ use redis::RedisError; use tokio::sync::Semaphore; use super::PayloadToSave; -use crate::app::callback::CallbackRequest; +use crate::app::callback::Callback; use crate::config::RedisConfig; use crate::consts::SAVED; use crate::Error; @@ -130,7 +130,7 @@ impl super::Store for RedisConnection { Ok(()) } - async fn retrieve_callbacks(&mut self, id: &str) -> Result>, Error> { + async fn retrieve_callbacks(&mut self, id: &str) -> Result>, Error> { let result: Result>, RedisError> = redis::cmd(LRANGE) .arg(id) .arg(0) @@ -147,7 +147,7 @@ impl super::Store for RedisConnection { let messages: Result, _> = result .into_iter() .map(|msg| { - let cbr: CallbackRequest = serde_json::from_slice(&msg).map_err(|e| { + let cbr: Callback = serde_json::from_slice(&msg).map_err(|e| { Error::StoreRead(format!("Parsing payload from bytes - {}", e)) })?; Ok(Arc::new(cbr)) @@ -201,11 +201,11 @@ impl super::Store for RedisConnection { #[cfg(feature = "redis-tests")] #[cfg(test)] mod tests { - use axum::body::Bytes; - use redis::AsyncCommands; - use super::*; use crate::app::callback::store::LocalStore; + use crate::app::callback::Response; + use axum::body::Bytes; + use redis::AsyncCommands; #[tokio::test] async fn test_redis_store() { @@ -225,12 +225,12 @@ mod tests { let ps_cb = PayloadToSave::Callback { key: key.clone(), - value: Arc::new(CallbackRequest { + value: Arc::new(Callback { id: String::from("1234"), vertex: String::from("prev_vertex"), cb_time: 1234, from_vertex: String::from("next_vertex"), - tags: None, + responses: vec![Response { tags: None }], }), }; @@ -290,12 +290,12 @@ mod tests { .expect("Failed to connect to Redis"); let key = uuid::Uuid::new_v4().to_string(); - let value = Arc::new(CallbackRequest { + let value = Arc::new(Callback { id: String::from("test-redis-ttl"), vertex: String::from("vertex"), cb_time: 1234, from_vertex: String::from("next_vertex"), - tags: None, + responses: vec![Response { tags: None }], }); // Save with TTL of 1 second diff --git a/rust/serving/src/app/jetstream_proxy.rs b/rust/serving/src/app/jetstream_proxy.rs index eb083d57e..6d4d9bb73 100644 --- a/rust/serving/src/app/jetstream_proxy.rs +++ b/rust/serving/src/app/jetstream_proxy.rs @@ -260,10 +260,11 @@ mod tests { use tower::ServiceExt; use super::*; + use crate::app::callback; use crate::app::callback::state::State as CallbackState; use crate::app::callback::store::memstore::InMemoryStore; use crate::app::callback::store::PayloadToSave; - use crate::app::callback::CallbackRequest; + use crate::app::callback::Callback; use crate::app::tracker::MessageGraph; use crate::pipeline::PipelineDCG; use crate::{Error, Settings}; @@ -277,10 +278,7 @@ mod tests { async fn save(&mut self, _messages: Vec) -> crate::Result<()> { Ok(()) } - async fn retrieve_callbacks( - &mut self, - _id: &str, - ) -> Result>, Error> { + async fn retrieve_callbacks(&mut self, _id: &str) -> Result>, Error> { Ok(vec![]) } async fn retrieve_datum(&mut self, _id: &str) -> Result>, Error> { @@ -355,28 +353,28 @@ mod tests { resp } - fn create_default_callbacks(id: &str) -> Vec { + fn create_default_callbacks(id: &str) -> Vec { vec![ - CallbackRequest { + Callback { id: id.to_string(), vertex: "in".to_string(), cb_time: 12345, from_vertex: "in".to_string(), - tags: None, + responses: vec![callback::Response { tags: None }], }, - CallbackRequest { + Callback { id: id.to_string(), vertex: "cat".to_string(), cb_time: 12345, from_vertex: "in".to_string(), - tags: None, + responses: vec![callback::Response { tags: None }], }, - CallbackRequest { + Callback { id: id.to_string(), vertex: "out".to_string(), cb_time: 12345, from_vertex: "cat".to_string(), - tags: None, + responses: vec![callback::Response { tags: None }], }, ] } @@ -417,10 +415,10 @@ mod tests { let app = jetstream_proxy(app_state).await.unwrap(); tokio::spawn(async move { - let cbs = create_default_callbacks(ID_VALUE); let mut retries = 0; loop { - match callback_state.insert_callback_requests(cbs.clone()).await { + let cbs = create_default_callbacks(ID_VALUE); + match callback_state.insert_callback_requests(cbs).await { Ok(_) => break, Err(e) => { retries += 1; @@ -489,14 +487,14 @@ mod tests { let app = jetstream_proxy(app_state).await.unwrap(); // pipeline is in -> cat -> out, so we will have 3 callback requests - let cbs = create_default_callbacks(ID_VALUE); // spawn a tokio task which will insert the callback requests to the callback state // if it fails, sleep for 10ms and retry tokio::spawn(async move { let mut retries = 0; loop { - match callback_state.insert_callback_requests(cbs.clone()).await { + let cbs = create_default_callbacks(ID_VALUE); + match callback_state.insert_callback_requests(cbs).await { Ok(_) => { // save a test message, we should get this message when serve is invoked // with foobar id diff --git a/rust/serving/src/app/tracker.rs b/rust/serving/src/app/tracker.rs index 33137f45d..5f3b24db7 100644 --- a/rust/serving/src/app/tracker.rs +++ b/rust/serving/src/app/tracker.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use serde::{Deserialize, Serialize}; -use crate::app::callback::CallbackRequest; +use crate::app::callback::Callback; use crate::pipeline::{Edge, OperatorType, PipelineDCG}; use crate::Error; @@ -16,6 +16,8 @@ fn compare_slice(operator: &OperatorType, a: &[String], b: &[String]) -> bool { } } +/// hash map of vertex and its edges. The key of the HashMap and `Edge.from` are same, `Edge.to` will +/// help find the adjacent vertex. type Graph = HashMap>; #[derive(Serialize, Deserialize, Debug)] @@ -24,8 +26,6 @@ struct Subgraph { blocks: Vec, } -const DROP: &str = "U+005C__DROP__"; - /// MessageGraph is a struct that generates the graph from the source vertex to the downstream vertices /// for a message using the given callbacks. pub(crate) struct MessageGraph { @@ -44,19 +44,21 @@ pub(crate) struct Block { // whether it has been visited or not. It is used to keep track of the visited callbacks. #[derive(Debug)] struct CallbackRequestWrapper { - callback_request: Arc, + callback_request: Arc, visited: bool, } impl MessageGraph { - /// This function generates a sub graph from a list of callbacks. + /// Generates a sub graph from a list of [Callback]. /// It first creates a HashMap to map each vertex to its corresponding callbacks. - /// Then it finds the source vertex by checking if the vertex and from_vertex fields are the same. - /// Finally, it calls the `generate_subgraph` function to generate the subgraph from the source vertex. + /// NOTE: it finds checks whether the callback is from the originating source vertex by checking + /// if the vertex and from_vertex fields are the same. + /// Finally, it calls the [Self::generate_subgraph] function to generate the subgraph from the source + /// vertex. pub(crate) fn generate_subgraph_from_callbacks( &self, id: String, - callbacks: Vec>, + callbacks: Vec>, ) -> Result, Error> { // Create a HashMap to map each vertex to its corresponding callbacks let mut callback_map: HashMap> = HashMap::new(); @@ -115,10 +117,13 @@ impl MessageGraph { } } - // generate_subgraph function is a recursive function that generates the sub graph from the source vertex for - // the given list of callbacks. The function returns true if the subgraph is generated successfully(if we are - // able to find a subgraph for the message using the given callbacks), it - // updates the subgraph with the path from the source vertex to the downstream vertices. + /// generate_subgraph function is a recursive function that generates the sub graph from the source + /// vertex for the given list of callbacks. The function returns true if the subgraph is + /// generated successfully (if we are able to find a subgraph for the message using the given + /// callbacks), it updates the subgraph with the path from the source vertex to the downstream + /// vertices. + /// It uses the pipeline DAG [Graph] and the [Callback]'s HashMap to check whether sub-graph is + /// complete. fn generate_subgraph( &self, current: String, @@ -126,7 +131,7 @@ impl MessageGraph { callback_map: &mut HashMap>, subgraph: &mut Subgraph, ) -> bool { - let mut current_callback: Option> = None; + let mut current_callback: Option> = None; // we need to borrow the callback_map as mutable to update the visited flag of the callback // so that next time when we visit the same callback, we can skip it. Because there can be cases @@ -160,67 +165,74 @@ impl MessageGraph { cb_time: current_callback.cb_time, }); - // if the current vertex has a DROP tag, then we should not proceed further - // and return true - if current_callback - .tags - .as_ref() - .map_or(false, |tags| tags.contains(&DROP.to_string())) - { + // if there are no responses, means the message is dropped, we can return true + if current_callback.responses.is_empty() { return true; } - // recursively invoke the downstream vertices of the current vertex, if any - if let Some(edges) = self.dag.get(¤t) { - for edge in edges { - // check if the edge should proceed based on the conditions - // if there are no conditions, we should proceed with the edge - // if there are conditions, we should check the tags of the current callback - // with the tags of the edge and the operator of the tags to decide if we should - // proceed with the edge - let should_proceed = edge - .conditions - .as_ref() - // If the edge has conditions, get the tags - .and_then(|conditions| conditions.tags.as_ref()) - // If there are no conditions or tags, default to true (i.e., proceed with the edge) - // If there are tags, compare the tags with the current callback's tags and the operator - // to decide if we should proceed with the edge. - .map_or(true, |tags| { - current_callback - .tags - .as_ref() - // If the current callback has no tags we should not proceed with the edge for "and" and "or" operators - // because we expect the current callback to have tags specified in the edge. - // If the current callback has no tags we should proceed with the edge for "not" operator. - // because we don't expect the current callback to have tags specified in the edge. - .map_or( - tags.operator.as_ref() == Some(&OperatorType::Not), - |callback_tags| { - tags.operator.as_ref().map_or(false, |operator| { - // If there is no operator, default to false (i.e., do not proceed with the edge) - // If there is an operator, compare the current callback's tags with the edge's tags - compare_slice(operator, callback_tags, &tags.values) - }) - }, - ) - }); - - // if the conditions are not met, then proceed to the next edge - if !should_proceed { - continue; - } - - // proceed to the downstream vertex - // if any of the downstream vertex returns false, then we should return false. - if !self.generate_subgraph(edge.to.clone(), current.clone(), callback_map, subgraph) - { - return false; + // iterate over the responses of the current callback, for flatmap operation there can + // more than one response, so we need to make sure all the responses are processed. + // For example a -> b -> c, lets say vertex a has 2 responses. We will have to recursively + // find the subgraph for both the responses. + for response in current_callback.responses.iter() { + // recursively invoke the downstream vertices of the current vertex, if any + if let Some(edges) = self.dag.get(¤t) { + for edge in edges { + // check if the edge should proceed based on the conditions + // if there are no conditions, we should proceed with the edge + // if there are conditions, we should check the tags of the current callback + // with the tags of the edge and the operator of the tags to decide if we should + // proceed with the edge + let will_continue_the_path = edge + .conditions + .as_ref() + // If the edge has conditions, get the tags + .and_then(|conditions| conditions.tags.as_ref()) + // If there are no conditions or tags, default to true (i.e., proceed with the edge) + // If there are tags, compare the tags with the current callback's tags and the operator + // to decide if we should proceed with the edge. + .map_or(true, |tags| { + response + .tags + .as_ref() + // If the current callback has no tags we should not proceed with the edge for "and" and "or" operators + // because we expect the current callback to have tags specified in the edge. + // If the current callback has no tags we should proceed with the edge for "not" operator. + // because we don't expect the current callback to have tags specified in the edge. + .map_or( + tags.operator.as_ref() == Some(&OperatorType::Not), + |callback_tags| { + tags.operator.as_ref().map_or(false, |operator| { + // If there is no operator, default to false (i.e., do not proceed with the edge) + // If there is an operator, compare the current callback's tags with the edge's tags + compare_slice(operator, callback_tags, &tags.values) + }) + }, + ) + }); + + // if the conditions are not met, then proceed to the next edge because conditions + // for forwarding the message did not match the conditional forwarding requirements. + if !will_continue_the_path { + // let's move on to the next edge + continue; + } + + // recursively proceed to the next downstream vertex + // if any of the downstream vertex returns false, then we should return false. + if !self.generate_subgraph( + edge.to.clone(), + current.clone(), + callback_map, + subgraph, + ) { + return false; + } } } + // if there are no downstream vertices, or all the downstream vertices returned true, + // we can return true } - // if there are no downstream vertices, or all the downstream vertices returned true, - // we can return true true } @@ -238,6 +250,7 @@ impl MessageGraph { #[cfg(test)] mod tests { use super::*; + use crate::app::callback::Response; use crate::pipeline::{Conditions, Tag, Vertex}; #[test] @@ -264,12 +277,12 @@ mod tests { callback_map.insert( "a".to_string(), vec![CallbackRequestWrapper { - callback_request: Arc::new(CallbackRequest { + callback_request: Arc::new(Callback { id: "uuid1".to_string(), vertex: "a".to_string(), cb_time: 1, from_vertex: "a".to_string(), - tags: None, + responses: vec![Response { tags: None }], }), visited: false, }], @@ -313,12 +326,12 @@ mod tests { callback_map.insert( "a".to_string(), vec![CallbackRequestWrapper { - callback_request: Arc::new(CallbackRequest { + callback_request: Arc::new(Callback { id: "uuid1".to_string(), vertex: "a".to_string(), cb_time: 1, from_vertex: "a".to_string(), - tags: None, + responses: vec![Response { tags: None }], }), visited: false, }], @@ -327,12 +340,12 @@ mod tests { callback_map.insert( "b".to_string(), vec![CallbackRequestWrapper { - callback_request: Arc::new(CallbackRequest { + callback_request: Arc::new(Callback { id: "uuid1".to_string(), vertex: "b".to_string(), cb_time: 1, from_vertex: "a".to_string(), - tags: None, + responses: vec![Response { tags: None }], }), visited: false, }], @@ -341,12 +354,12 @@ mod tests { callback_map.insert( "c".to_string(), vec![CallbackRequestWrapper { - callback_request: Arc::new(CallbackRequest { + callback_request: Arc::new(Callback { id: "uuid1".to_string(), vertex: "c".to_string(), cb_time: 1, from_vertex: "a".to_string(), - tags: None, + responses: vec![Response { tags: None }], }), visited: false, }], @@ -461,77 +474,86 @@ mod tests { let source_vertex = "a".to_string(); let raw_callback = r#"[ - { - "id": "xxxx", - "vertex": "a", - "from_vertex": "a", - "cb_time": 123456789 - }, - { - "id": "xxxx", - "vertex": "b", - "from_vertex": "a", - "cb_time": 123456867 - }, - { - "id": "xxxx", - "vertex": "c", - "from_vertex": "a", - "cb_time": 123456819 - }, - { - "id": "xxxx", - "vertex": "d", - "from_vertex": "b", - "cb_time": 123456840 - }, - { - "id": "xxxx", - "vertex": "e", - "from_vertex": "c", - "cb_time": 123456843 - }, - { - "id": "xxxx", - "vertex": "f", - "from_vertex": "d", - "cb_time": 123456854 - }, - { - "id": "xxxx", - "vertex": "f", - "from_vertex": "e", - "cb_time": 123456886 - }, - { - "id": "xxxx", - "vertex": "g", - "from_vertex": "f", - "tags": ["even"], - "cb_time": 123456885 - }, - { - "id": "xxxx", - "vertex": "g", - "from_vertex": "f", - "tags": ["even"], - "cb_time": 123456888 - }, - { - "id": "xxxx", - "vertex": "h", - "from_vertex": "g", - "cb_time": 123456889 - }, - { - "id": "xxxx", - "vertex": "h", - "from_vertex": "g", - "cb_time": 123456890 - } - ]"#; + { + "id": "xxxx", + "vertex": "a", + "from_vertex": "a", + "cb_time": 123456789, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "b", + "from_vertex": "a", + "cb_time": 123456867, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "c", + "from_vertex": "a", + "cb_time": 123456819, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "d", + "from_vertex": "b", + "cb_time": 123456840, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "e", + "from_vertex": "c", + "cb_time": 123456843, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "f", + "from_vertex": "d", + "cb_time": 123456854, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "f", + "from_vertex": "e", + "cb_time": 123456886, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "g", + "from_vertex": "f", + "cb_time": 123456885, + "responses": [{"tags": ["even"]}] + }, + { + "id": "xxxx", + "vertex": "g", + "from_vertex": "f", + "cb_time": 123456888, + "responses": [{"tags": ["even"]}] + }, + { + "id": "xxxx", + "vertex": "h", + "from_vertex": "g", + "cb_time": 123456889, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "h", + "from_vertex": "g", + "cb_time": 123456890, + "responses": [{"tags": null}] + } + ]"#; - let callbacks: Vec = serde_json::from_str(raw_callback).unwrap(); + let callbacks: Vec = serde_json::from_str(raw_callback).unwrap(); let mut callback_map: HashMap> = HashMap::new(); for callback in callbacks { @@ -590,23 +612,24 @@ mod tests { let source_vertex = "a".to_string(); let raw_callback = r#" - [ - { - "id": "xxxx", - "vertex": "a", - "from_vertex": "a", - "cb_time": 123456789 - }, - { - "id": "xxxx", - "vertex": "b", - "from_vertex": "a", - "cb_time": 123456867, - "tags": ["U+005C__DROP__"] - } - ]"#; + [ + { + "id": "xxxx", + "vertex": "a", + "from_vertex": "a", + "cb_time": 123456789, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "b", + "from_vertex": "a", + "cb_time": 123456867, + "responses": [] + } + ]"#; - let callbacks: Vec = serde_json::from_str(raw_callback).unwrap(); + let callbacks: Vec = serde_json::from_str(raw_callback).unwrap(); let mut callback_map: HashMap> = HashMap::new(); for callback in callbacks { @@ -728,54 +751,59 @@ mod tests { let source_vertex = "a".to_string(); let raw_callback = r#" - [ - { - "id": "xxxx", - "vertex": "a", - "from_vertex": "a", - "cb_time": 123456789 - }, - { - "id": "xxxx", - "vertex": "b", - "from_vertex": "a", - "cb_time": 123456867 - }, - { - "id": "xxxx", - "vertex": "c", - "from_vertex": "a", - "cb_time": 123456819, - "tags": ["U+005C__DROP__"] - }, - { - "id": "xxxx", - "vertex": "d", - "from_vertex": "b", - "cb_time": 123456840 - }, - { - "id": "xxxx", - "vertex": "f", - "from_vertex": "d", - "cb_time": 123456854 - }, - { - "id": "xxxx", - "vertex": "g", - "from_vertex": "f", - "tags": ["even"], - "cb_time": 123456885 - }, - { - "id": "xxxx", - "vertex": "h", - "from_vertex": "g", - "cb_time": 123456889 - } - ]"#; + [ + { + "id": "xxxx", + "vertex": "a", + "from_vertex": "a", + "cb_time": 123456789, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "b", + "from_vertex": "a", + "cb_time": 123456867, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "c", + "from_vertex": "a", + "cb_time": 123456819, + "responses": [] + }, + { + "id": "xxxx", + "vertex": "d", + "from_vertex": "b", + "cb_time": 123456840, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "f", + "from_vertex": "d", + "cb_time": 123456854, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "g", + "from_vertex": "f", + "cb_time": 123456885, + "responses": [{"tags": ["even"]}] + }, + { + "id": "xxxx", + "vertex": "h", + "from_vertex": "g", + "cb_time": 123456889, + "responses": [{"tags": null}] + } + ]"#; - let callbacks: Vec = serde_json::from_str(raw_callback).unwrap(); + let callbacks: Vec = serde_json::from_str(raw_callback).unwrap(); let mut callback_map: HashMap> = HashMap::new(); for callback in callbacks { @@ -844,36 +872,43 @@ mod tests { "id": "xxxx", "vertex": "a", "from_vertex": "a", - "cb_time": 123456789 + "cb_time": 123456789, + "responses": [{"tags": null}] }, { "id": "xxxx", "vertex": "b", "from_vertex": "a", "cb_time": 123456867, - "tags": ["failed"] + "responses": [{"tags": ["failed"]}] }, { "id": "xxxx", "vertex": "a", "from_vertex": "b", - "cb_time": 123456819 + "cb_time": 123456819, + "responses": [{"tags": null}] + }, { "id": "xxxx", "vertex": "b", "from_vertex": "a", - "cb_time": 123456819 + "cb_time": 123456819, + "responses": [{"tags": null}] + }, { "id": "xxxx", "vertex": "c", "from_vertex": "b", - "cb_time": 123456819 + "cb_time": 123456819, + "responses": [{"tags": null}] + } ]"#; - let callbacks: Vec = serde_json::from_str(raw_callback).unwrap(); + let callbacks: Vec = serde_json::from_str(raw_callback).unwrap(); let mut callback_map: HashMap> = HashMap::new(); for callback in callbacks { @@ -915,12 +950,12 @@ mod tests { let message_graph = MessageGraph { dag }; // Create a callback with an invalid vertex - let callbacks = vec![Arc::new(CallbackRequest { + let callbacks = vec![Arc::new(Callback { id: "test".to_string(), vertex: "invalid_vertex".to_string(), from_vertex: "invalid_vertex".to_string(), cb_time: 1, - tags: None, + responses: vec![Response { tags: None }], })]; // Call the function with the invalid callback @@ -930,4 +965,415 @@ mod tests { assert!(result.is_err()); assert!(matches!(result, Err(Error::SubGraphInvalidInput(_)))); } + + #[test] + fn test_flatmap_operation_with_simple_dag() { + let pipeline = PipelineDCG { + vertices: vec![ + Vertex { + name: "a".to_string(), + }, + Vertex { + name: "b".to_string(), + }, + Vertex { + name: "c".to_string(), + }, + ], + edges: vec![ + Edge { + from: "a".to_string(), + to: "b".to_string(), + conditions: None, + }, + Edge { + from: "b".to_string(), + to: "c".to_string(), + conditions: None, + }, + ], + }; + + let message_graph = MessageGraph::from_pipeline(&pipeline).unwrap(); + let source_vertex = "a".to_string(); + + let raw_callback = r#"[ + { + "id": "xxxx", + "vertex": "a", + "from_vertex": "a", + "cb_time": 123456789, + "responses": [ + {"tags": null}, + {"index": 1, "tags": null} + ] + }, + { + "id": "xxxx", + "vertex": "b", + "from_vertex": "a", + "cb_time": 123456867, + "responses": [ + {"tags": null}, + {"index": 1, "tags": null} + ] + }, + { + "id": "xxxx", + "vertex": "b", + "from_vertex": "a", + "cb_time": 123456868, + "responses": [ + {"tags": null}, + {"index": 1, "tags": null} + ] + }, + { + "id": "xxxx", + "vertex": "c", + "from_vertex": "b", + "cb_time": 123456869, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "c", + "from_vertex": "b", + "cb_time": 123456870, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "c", + "from_vertex": "b", + "cb_time": 123456871, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "c", + "from_vertex": "b", + "cb_time": 123456872, + "responses": [{"tags": null}] + } + ]"#; + + let callbacks: Vec = serde_json::from_str(raw_callback).unwrap(); + let mut callback_map: HashMap> = HashMap::new(); + + for callback in callbacks { + callback_map + .entry(callback.vertex.clone()) + .or_default() + .push(CallbackRequestWrapper { + callback_request: Arc::new(callback), + visited: false, + }); + } + + let mut subgraph: Subgraph = Subgraph { + id: "xxxx".to_string(), + blocks: Vec::new(), + }; + let result = message_graph.generate_subgraph( + source_vertex.clone(), + source_vertex, + &mut callback_map, + &mut subgraph, + ); + + assert!(result); + } + + #[test] + fn test_flatmap_operation_with_complex_dag() { + let pipeline = PipelineDCG { + vertices: vec![ + Vertex { + name: "a".to_string(), + }, + Vertex { + name: "b".to_string(), + }, + Vertex { + name: "c".to_string(), + }, + Vertex { + name: "d".to_string(), + }, + Vertex { + name: "e".to_string(), + }, + Vertex { + name: "f".to_string(), + }, + Vertex { + name: "g".to_string(), + }, + Vertex { + name: "h".to_string(), + }, + Vertex { + name: "i".to_string(), + }, + ], + edges: vec![ + Edge { + from: "a".to_string(), + to: "b".to_string(), + conditions: None, + }, + Edge { + from: "a".to_string(), + to: "c".to_string(), + conditions: None, + }, + Edge { + from: "b".to_string(), + to: "d".to_string(), + conditions: None, + }, + Edge { + from: "c".to_string(), + to: "e".to_string(), + conditions: None, + }, + Edge { + from: "d".to_string(), + to: "f".to_string(), + conditions: None, + }, + Edge { + from: "e".to_string(), + to: "f".to_string(), + conditions: None, + }, + Edge { + from: "f".to_string(), + to: "g".to_string(), + conditions: None, + }, + Edge { + from: "g".to_string(), + to: "h".to_string(), + conditions: Some(Conditions { + tags: Some(Tag { + operator: Some(OperatorType::And), + values: vec!["even".to_string()], + }), + }), + }, + Edge { + from: "g".to_string(), + to: "i".to_string(), + conditions: Some(Conditions { + tags: Some(Tag { + operator: Some(OperatorType::Or), + values: vec!["odd".to_string()], + }), + }), + }, + ], + }; + + let message_graph = MessageGraph::from_pipeline(&pipeline).unwrap(); + let source_vertex = "a".to_string(); + + let raw_callback = r#"[ + { + "id": "xxxx", + "vertex": "a", + "from_vertex": "a", + "cb_time": 123456789, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "b", + "from_vertex": "a", + "cb_time": 123456867, + "responses": [ + {"tags": null}, + {"index": 1, "tags": null} + ] + }, + { + "id": "xxxx", + "vertex": "c", + "from_vertex": "a", + "cb_time": 123456819, + "responses": [ + {"tags": null}, + {"index": 1, "tags": null}, + {"index": 2, "tags": null} + ] + }, + { + "id": "xxxx", + "vertex": "d", + "from_vertex": "b", + "cb_time": 123456840, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "d", + "from_vertex": "b", + "cb_time": 123456841, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "e", + "from_vertex": "c", + "cb_time": 123456843, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "e", + "from_vertex": "c", + "cb_time": 123456844, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "e", + "from_vertex": "c", + "cb_time": 123456845, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "f", + "from_vertex": "d", + "cb_time": 123456854, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "f", + "from_vertex": "d", + "cb_time": 123456854, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "f", + "from_vertex": "e", + "cb_time": 123456886, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "f", + "from_vertex": "e", + "cb_time": 123456887, + "responses": [{"index": 1, "tags": null}] + }, + { + "id": "xxxx", + "vertex": "f", + "from_vertex": "e", + "cb_time": 123456888, + "responses": [{"index": 2, "tags": null}] + }, + { + "id": "xxxx", + "vertex": "g", + "from_vertex": "f", + "cb_time": 123456885, + "responses": [{"tags": ["even"]}] + }, + { + "id": "xxxx", + "vertex": "g", + "from_vertex": "f", + "cb_time": 123456886, + "responses": [{"index": 1, "tags": ["even"]}] + }, + { + "id": "xxxx", + "vertex": "g", + "from_vertex": "f", + "cb_time": 123456887, + "responses": [{"index": 2, "tags": ["odd"]}] + }, + { + "id": "xxxx", + "vertex": "g", + "from_vertex": "f", + "cb_time": 123456888, + "responses": [{"index": 3, "tags": ["odd"]}] + }, + { + "id": "xxxx", + "vertex": "g", + "from_vertex": "f", + "cb_time": 123456889, + "responses": [{"index": 4, "tags": ["odd"]}] + }, + { + "id": "xxxx", + "vertex": "h", + "from_vertex": "g", + "cb_time": 123456890, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "h", + "from_vertex": "g", + "cb_time": 123456891, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "i", + "from_vertex": "g", + "cb_time": 123456892, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "i", + "from_vertex": "g", + "cb_time": 123456893, + "responses": [{"tags": null}] + }, + { + "id": "xxxx", + "vertex": "i", + "from_vertex": "g", + "cb_time": 123456894, + "responses": [{"tags": null}] + } + ]"#; + + let callbacks: Vec = serde_json::from_str(raw_callback).unwrap(); + let mut callback_map: HashMap> = HashMap::new(); + + for callback in callbacks { + callback_map + .entry(callback.vertex.clone()) + .or_default() + .push(CallbackRequestWrapper { + callback_request: Arc::new(callback), + visited: false, + }); + } + + let mut subgraph: Subgraph = Subgraph { + id: "xxxx".to_string(), + blocks: Vec::new(), + }; + let result = message_graph.generate_subgraph( + source_vertex.clone(), + source_vertex, + &mut callback_map, + &mut subgraph, + ); + + assert!(result); + } }