diff --git a/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/BUILD b/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/BUILD
index caed6e13c058d3..745e6eaebae2b7 100644
--- a/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/BUILD
@@ -15,7 +15,9 @@ java_library(
srcs = glob(["*.java"]),
deps = [
"//src/main/java/com/google/devtools/build/lib/vfs",
+ "//third_party:auto_value",
"//third_party:error_prone_annotations",
+ "//third_party:gson",
"//third_party:guava",
],
)
diff --git a/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/GetCredentialsRequest.java b/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/GetCredentialsRequest.java
new file mode 100644
index 00000000000000..3915a861600732
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/GetCredentialsRequest.java
@@ -0,0 +1,103 @@
+// Copyright 2022 The Bazel Authors. All rights reserved.
+//
+// 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.
+
+package com.google.devtools.build.lib.authandtls.credentialhelper;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Preconditions;
+import com.google.errorprone.annotations.Immutable;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.annotations.JsonAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Locale;
+
+/**
+ * Request for the {@code get} command of the Credential
+ * Helper Protocol.
+ */
+@AutoValue
+@AutoValue.CopyAnnotations
+@Immutable
+@JsonAdapter(GetCredentialsRequest.GsonTypeAdapter.class)
+public abstract class GetCredentialsRequest {
+ /** Returns the {@link URI} this request is for. */
+ public abstract URI getUri();
+
+ /** Returns a new builder for {@link GetCredentialsRequest}. */
+ public static Builder newBuilder() {
+ return new AutoValue_GetCredentialsRequest.Builder();
+ }
+
+ /** Builder for {@link GetCredentialsRequest}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /** Sets the {@link URI} this request is for. */
+ public abstract Builder setUri(URI uri);
+
+ /** Returns the newly constructed {@link GetCredentialsRequest}. */
+ public abstract GetCredentialsRequest build();
+ }
+
+ /** GSON adapter for GetCredentialsRequest. */
+ public static final class GsonTypeAdapter extends TypeAdapter {
+ @Override
+ public void write(JsonWriter writer, GetCredentialsRequest value) throws IOException {
+ Preconditions.checkNotNull(writer);
+ Preconditions.checkNotNull(value);
+
+ writer.beginObject();
+ writer.name("uri").value(value.getUri().toString());
+ writer.endObject();
+ }
+
+ @Override
+ public GetCredentialsRequest read(JsonReader reader) throws IOException {
+ Preconditions.checkNotNull(reader);
+
+ Builder request = newBuilder();
+
+ if (reader.peek() != JsonToken.BEGIN_OBJECT) {
+ throw new JsonSyntaxException(
+ String.format(Locale.US, "Expected object, got %s", reader.peek()));
+ }
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String name = reader.nextName();
+ switch (name) {
+ case "uri":
+ if (reader.peek() != JsonToken.STRING) {
+ throw new JsonSyntaxException(
+ String.format(
+ Locale.US, "Expected value of 'url' to be a string, got %s", reader.peek()));
+ }
+ request.setUri(URI.create(reader.nextString()));
+ break;
+
+ default:
+ // We intentionally ignore unknown keys to achieve forward compatibility with requests
+ // coming from newer tools.
+ reader.skipValue();
+ }
+ }
+ reader.endObject();
+ return request.build();
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/GetCredentialsResponse.java b/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/GetCredentialsResponse.java
new file mode 100644
index 00000000000000..72f25cf4ddf196
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/GetCredentialsResponse.java
@@ -0,0 +1,154 @@
+// Copyright 2022 The Bazel Authors. All rights reserved.
+//
+// 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.
+
+package com.google.devtools.build.lib.authandtls.credentialhelper;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.errorprone.annotations.Immutable;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.annotations.JsonAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Response from the {@code get} command of the Credential
+ * Helper Protocol.
+ */
+@AutoValue
+@AutoValue.CopyAnnotations
+@Immutable
+@JsonAdapter(GetCredentialsResponse.GsonTypeAdapter.class)
+public abstract class GetCredentialsResponse {
+ /** Returns the headers to attach to the request. */
+ public abstract ImmutableMap> getHeaders();
+
+ /** Returns a new builder for {@link GetCredentialsRequest}. */
+ public static Builder newBuilder() {
+ return new AutoValue_GetCredentialsResponse.Builder();
+ }
+
+ /** Builder for {@link GetCredentialsResponse}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ protected abstract ImmutableMap.Builder> headersBuilder();
+
+ /** Returns the newly constructed {@link GetCredentialsResponse}. */
+ public abstract GetCredentialsResponse build();
+ }
+
+ /** GSON adapter for GetCredentialsResponse. */
+ public static final class GsonTypeAdapter extends TypeAdapter {
+ @Override
+ public void write(JsonWriter writer, GetCredentialsResponse response) throws IOException {
+ Preconditions.checkNotNull(writer);
+ Preconditions.checkNotNull(response);
+
+ writer.beginObject();
+
+ ImmutableMap> headers = response.getHeaders();
+ if (!headers.isEmpty()) {
+ writer.name("headers");
+ writer.beginObject();
+ for (Map.Entry> entry : headers.entrySet()) {
+ writer.name(entry.getKey());
+
+ writer.beginArray();
+ for (String value : entry.getValue()) {
+ writer.value(value);
+ }
+ writer.endArray();
+ }
+ writer.endObject();
+ }
+ writer.endObject();
+ }
+
+ @Override
+ public GetCredentialsResponse read(JsonReader reader) throws IOException {
+ Preconditions.checkNotNull(reader);
+
+ GetCredentialsResponse.Builder response = newBuilder();
+
+ if (reader.peek() != JsonToken.BEGIN_OBJECT) {
+ throw new JsonSyntaxException(
+ String.format(Locale.US, "Expected object, got %s", reader.peek()));
+ }
+ reader.beginObject();
+
+ while (reader.hasNext()) {
+ String name = reader.nextName();
+ switch (name) {
+ case "headers":
+ if (reader.peek() != JsonToken.BEGIN_OBJECT) {
+ throw new JsonSyntaxException(
+ String.format(
+ Locale.US,
+ "Expected value of 'headers' to be an object, got %s",
+ reader.peek()));
+ }
+ reader.beginObject();
+
+ while (reader.hasNext()) {
+ String headerName = reader.nextName();
+ ImmutableList.Builder headerValues = ImmutableList.builder();
+
+ if (reader.peek() != JsonToken.BEGIN_ARRAY) {
+ throw new JsonSyntaxException(
+ String.format(
+ Locale.US,
+ "Expected value of '%s' header to be an array of strings, got %s",
+ headerName,
+ reader.peek()));
+ }
+ reader.beginArray();
+ for (int i = 0; reader.hasNext(); i++) {
+ if (reader.peek() != JsonToken.STRING) {
+ throw new JsonSyntaxException(
+ String.format(
+ Locale.US,
+ "Expected value %s of '%s' header to be a string, got %s",
+ i,
+ headerName,
+ reader.peek()));
+ }
+ headerValues.add(reader.nextString());
+ }
+ reader.endArray();
+
+ response.headersBuilder().put(headerName, headerValues.build());
+ }
+
+ reader.endObject();
+ break;
+
+ default:
+ // We intentionally ignore unknown keys to achieve forward compatibility with responses
+ // coming from newer tools.
+ reader.skipValue();
+ }
+ }
+ reader.endObject();
+ return response.build();
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/authandtls/credentialhelper/BUILD b/src/test/java/com/google/devtools/build/lib/authandtls/credentialhelper/BUILD
index e904e6d99349fb..95b3ee4483cdc9 100644
--- a/src/test/java/com/google/devtools/build/lib/authandtls/credentialhelper/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/authandtls/credentialhelper/BUILD
@@ -26,6 +26,7 @@ java_test(
"//src/main/java/com/google/devtools/build/lib/vfs",
"//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
"//src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs",
+ "//third_party:gson",
"//third_party:guava",
"//third_party:junit4",
"//third_party:truth",
diff --git a/src/test/java/com/google/devtools/build/lib/authandtls/credentialhelper/GetCredentialsRequestTest.java b/src/test/java/com/google/devtools/build/lib/authandtls/credentialhelper/GetCredentialsRequestTest.java
new file mode 100644
index 00000000000000..0e0939ef551ca1
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/authandtls/credentialhelper/GetCredentialsRequestTest.java
@@ -0,0 +1,116 @@
+// Copyright 2022 The Bazel Authors. All rights reserved.
+//
+// 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.
+
+package com.google.devtools.build.lib.authandtls.credentialhelper;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+import java.net.URI;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link GetCredentialsRequest}. */
+@RunWith(JUnit4.class)
+public class GetCredentialsRequestTest {
+ private static final Gson GSON = new Gson();
+
+ @Test
+ public void parseValid() {
+ assertThat(
+ GSON.fromJson("{\"uri\": \"http://example.com\"}", GetCredentialsRequest.class)
+ .getUri())
+ .isEqualTo(URI.create("http://example.com"));
+ assertThat(
+ GSON.fromJson("{\"uri\": \"https://example.com\"}", GetCredentialsRequest.class)
+ .getUri())
+ .isEqualTo(URI.create("https://example.com"));
+ assertThat(
+ GSON.fromJson("{\"uri\": \"grpc://example.com\"}", GetCredentialsRequest.class)
+ .getUri())
+ .isEqualTo(URI.create("grpc://example.com"));
+ assertThat(
+ GSON.fromJson("{\"uri\": \"grpcs://example.com\"}", GetCredentialsRequest.class)
+ .getUri())
+ .isEqualTo(URI.create("grpcs://example.com"));
+
+ assertThat(
+ GSON.fromJson("{\"uri\": \"uri-without-protocol\"}", GetCredentialsRequest.class)
+ .getUri())
+ .isEqualTo(URI.create("uri-without-protocol"));
+ }
+
+ @Test
+ public void parseMissingUri() {
+ assertThrows(JsonSyntaxException.class, () -> GSON.fromJson("{}", GetCredentialsRequest.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"foo\": 1}", GetCredentialsRequest.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"foo\": 1, \"bar\": 2}", GetCredentialsRequest.class));
+ }
+
+ @Test
+ public void parseNonStringUri() {
+ assertThrows(JsonSyntaxException.class, () -> GSON.fromJson("[]", GetCredentialsRequest.class));
+ assertThrows(
+ JsonSyntaxException.class, () -> GSON.fromJson("\"foo\"", GetCredentialsRequest.class));
+ assertThrows(JsonSyntaxException.class, () -> GSON.fromJson("1", GetCredentialsRequest.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"uri\": 1}", GetCredentialsRequest.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"uri\": {}}", GetCredentialsRequest.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"uri\": []}", GetCredentialsRequest.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"uri\": [\"https://example.com\"]}", GetCredentialsRequest.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"uri\": null}", GetCredentialsRequest.class));
+ }
+
+ @Test
+ public void parseWithExtraFields() {
+ assertThat(
+ GSON.fromJson(
+ "{\"uri\": \"http://example.com\", \"foo\": 1}", GetCredentialsRequest.class)
+ .getUri())
+ .isEqualTo(URI.create("http://example.com"));
+ assertThat(
+ GSON.fromJson(
+ "{\"foo\": 1, \"uri\": \"http://example.com\"}", GetCredentialsRequest.class)
+ .getUri())
+ .isEqualTo(URI.create("http://example.com"));
+ assertThat(
+ GSON.fromJson(
+ "{\"uri\": \"http://example.com\", \"foo\": 1, \"bar\": {}}",
+ GetCredentialsRequest.class)
+ .getUri())
+ .isEqualTo(URI.create("http://example.com"));
+ assertThat(
+ GSON.fromJson(
+ "{\"foo\": 1, \"uri\": \"http://example.com\", \"bar\": []}",
+ GetCredentialsRequest.class)
+ .getUri())
+ .isEqualTo(URI.create("http://example.com"));
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/authandtls/credentialhelper/GetCredentialsResponseTest.java b/src/test/java/com/google/devtools/build/lib/authandtls/credentialhelper/GetCredentialsResponseTest.java
new file mode 100644
index 00000000000000..8d1d8f56c34ef2
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/authandtls/credentialhelper/GetCredentialsResponseTest.java
@@ -0,0 +1,158 @@
+// Copyright 2022 The Bazel Authors. All rights reserved.
+//
+// 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.
+
+package com.google.devtools.build.lib.authandtls.credentialhelper;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link GetCredentialsResponse}. */
+@RunWith(JUnit4.class)
+public class GetCredentialsResponseTest {
+ private static final Gson GSON = new Gson();
+
+ @Test
+ public void parseValid() {
+ assertThat(GSON.fromJson("{}", GetCredentialsResponse.class).getHeaders()).isEmpty();
+ assertThat(GSON.fromJson("{\"headers\": {}}", GetCredentialsResponse.class).getHeaders())
+ .isEmpty();
+
+ GetCredentialsResponse.Builder expectedResponseBuilder = GetCredentialsResponse.newBuilder();
+ expectedResponseBuilder.headersBuilder().put("a", ImmutableList.of());
+ expectedResponseBuilder.headersBuilder().put("b", ImmutableList.of("b"));
+ expectedResponseBuilder.headersBuilder().put("c", ImmutableList.of("c", "c"));
+ GetCredentialsResponse expectedResponse = expectedResponseBuilder.build();
+
+ assertThat(
+ GSON.fromJson(
+ "{\"headers\": {\"c\": [\"c\", \"c\"], \"a\": [], \"b\": [\"b\"]}}",
+ GetCredentialsResponse.class))
+ .isEqualTo(expectedResponse);
+ }
+
+ @Test
+ public void parseWithExtraFields() {
+ assertThat(GSON.fromJson("{\"foo\": 123}", GetCredentialsResponse.class).getHeaders())
+ .isEmpty();
+ assertThat(
+ GSON.fromJson("{\"foo\": 123, \"bar\": []}", GetCredentialsResponse.class).getHeaders())
+ .isEmpty();
+
+ GetCredentialsResponse.Builder expectedResponseBuilder = GetCredentialsResponse.newBuilder();
+ expectedResponseBuilder.headersBuilder().put("a", ImmutableList.of());
+ expectedResponseBuilder.headersBuilder().put("b", ImmutableList.of("b"));
+ expectedResponseBuilder.headersBuilder().put("c", ImmutableList.of("c", "c"));
+ GetCredentialsResponse expectedResponse = expectedResponseBuilder.build();
+
+ assertThat(
+ GSON.fromJson(
+ "{\"foo\": 123, \"headers\": {\"c\": [\"c\", \"c\"], \"a\": [], \"b\": [\"b\"]},"
+ + " \"bar\": 123}",
+ GetCredentialsResponse.class))
+ .isEqualTo(expectedResponse);
+ }
+
+ @Test
+ public void parseInvalid() {
+ assertThrows(
+ JsonSyntaxException.class, () -> GSON.fromJson("[]", GetCredentialsResponse.class));
+ assertThrows(
+ JsonSyntaxException.class, () -> GSON.fromJson("\"foo\"", GetCredentialsResponse.class));
+ assertThrows(JsonSyntaxException.class, () -> GSON.fromJson("1", GetCredentialsResponse.class));
+ }
+
+ @Test
+ public void parseInvalidHeadersEnvelope() {
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"headers\": null}", GetCredentialsResponse.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"headers\": \"foo\"}", GetCredentialsResponse.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"headers\": []}", GetCredentialsResponse.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"headers\": 1}", GetCredentialsResponse.class));
+ }
+
+ @Test
+ public void parseInvalidHeadersValue() {
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"headers\": {\"a\": null}}", GetCredentialsResponse.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"headers\": {\"a\": 1}}", GetCredentialsResponse.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"headers\": {\"a\": {}}}", GetCredentialsResponse.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"headers\": {\"a\": \"a\"}}", GetCredentialsResponse.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"headers\": {\"a\": [null]}}", GetCredentialsResponse.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"headers\": {\"a\": [\"a\", null]}}", GetCredentialsResponse.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"headers\": {\"a\": [null, \"a\"]}}", GetCredentialsResponse.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"headers\": {\"a\": [\"a\", 1]}}", GetCredentialsResponse.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"headers\": {\"a\": [1, \"a\"]}}", GetCredentialsResponse.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"headers\": {\"a\": [\"a\", []]}}", GetCredentialsResponse.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"headers\": {\"a\": [[], \"a\"]}}", GetCredentialsResponse.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"headers\": {\"a\": [\"a\", {}]}}", GetCredentialsResponse.class));
+ assertThrows(
+ JsonSyntaxException.class,
+ () -> GSON.fromJson("{\"headers\": {\"a\": [{}, \"a\"]}}", GetCredentialsResponse.class));
+ }
+
+ @Test
+ public void serializeEmptyHeaders() {
+ GetCredentialsResponse expectedResponse = GetCredentialsResponse.newBuilder().build();
+ assertThat(GSON.toJson(expectedResponse)).isEqualTo("{}");
+ }
+
+ @Test
+ public void roundTrip() {
+ GetCredentialsResponse.Builder expectedResponseBuilder = GetCredentialsResponse.newBuilder();
+ expectedResponseBuilder.headersBuilder().put("a", ImmutableList.of());
+ expectedResponseBuilder.headersBuilder().put("b", ImmutableList.of("b"));
+ expectedResponseBuilder.headersBuilder().put("c", ImmutableList.of("c", "c"));
+ GetCredentialsResponse expectedResponse = expectedResponseBuilder.build();
+
+ assertThat(GSON.fromJson(GSON.toJson(expectedResponse), GetCredentialsResponse.class))
+ .isEqualTo(expectedResponse);
+ }
+}