diff --git a/aws-http-auth/sigv4/stream.go b/aws-http-auth/sigv4/stream.go new file mode 100644 index 000000000..626915a08 --- /dev/null +++ b/aws-http-auth/sigv4/stream.go @@ -0,0 +1,82 @@ +package sigv4 + +import ( + "crypto/sha256" + "encoding/hex" + "hash" + "strings" + "time" + + "github.com/aws/smithy-go/aws-http-auth/credentials" + v4internal "github.com/aws/smithy-go/aws-http-auth/internal/v4" +) + +// EventStreamSigner implements SigV4 event stream message signing. +// +// Unlike request signing, event stream message signing is **stateful**. The +// signer must be seeded with an initial signature from the originating HTTP +// request, and each message is signed using the signature of the previous +// message (starting at that seed value). Therefore the caller **MUST NOT** +// reuse an instance of an EventStreamSigner across multiple streams. +type EventStreamSigner struct { + creds credentials.Credentials + service string + region string + prev []byte +} + +// EventStreamSignerOptions is reserved for future use. +type EventStreamSignerOptions struct{} + +// NewEventStreamSigner returns an EventStreamSigner for a single stream. +func NewEventStreamSigner(creds credentials.Credentials, service, region string, seed []byte, + opts ...func(*EventStreamSignerOptions)) *EventStreamSigner { + return &EventStreamSigner{ + creds: creds, + service: service, + region: region, + prev: seed, + } +} + +// SignMessage computes the signature for the next message in the event stream. +func (s *EventStreamSigner) SignMessage(headers, payload []byte, now time.Time) ([]byte, error) { + now = now.UTC() + + scopeNow := now.Format(v4internal.ShortTimeFormat) + key := deriveKey(s.creds.SecretAccessKey, s.service, s.region, scopeNow) + scope := strings.Join([]string{ + scopeNow, + s.region, + s.service, + "aws4_request", + }, "/") + + h := sha256.New() + toSign := strings.Join([]string{ + "AWS4-HMAC-SHA256-PAYLOAD", + now.Format(v4internal.TimeFormat), + scope, + hex.EncodeToString(s.prev), + hex.EncodeToString(mkhash(h, headers)), + hex.EncodeToString(mkhash(h, payload)), + }, "\n") + + signature := hmacSHA256(key, []byte(toSign)) + s.prev = signature + + return signature, nil +} + +func deriveKey(secret, service, region, shortDate string) []byte { + key := hmacSHA256([]byte("AWS4"+secret), []byte(shortDate)) + key = hmacSHA256(key, []byte(region)) + key = hmacSHA256(key, []byte(service)) + return hmacSHA256(key, []byte("aws4_request")) +} + +func mkhash(h hash.Hash, b []byte) []byte { + h.Reset() + h.Write(b) + return h.Sum(nil) +} diff --git a/aws-http-auth/sigv4/stream_test.go b/aws-http-auth/sigv4/stream_test.go new file mode 100644 index 000000000..cf6412644 --- /dev/null +++ b/aws-http-auth/sigv4/stream_test.go @@ -0,0 +1,81 @@ +package sigv4 + +import ( + "encoding/hex" + "testing" + "time" + + "github.com/aws/smithy-go/aws-http-auth/credentials" +) + +func mustHexDecode(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return b +} + +func TestEventStreamSigner_SignMessage(t *testing.T) { + signer := NewEventStreamSigner( + credentials.Credentials{ + AccessKeyID: "AKID", + SecretAccessKey: "SECRET", + }, + "transcribestreaming", + "us-east-1", + []byte("foobarbazqux"), + ) + + messages := []struct { + Name string + Headers []byte + Payload []byte + Time time.Time + Expect []byte + }{ + { + Name: "first message", + Headers: []byte("foo"), + Payload: []byte("foo"), + Time: time.Unix(10, 0), + Expect: mustHexDecode("e3a136405ec1152f62136742efd41f8639cd647a1cf21fab12aedb22cccd8999"), + }, + { + Name: "second message", + Headers: []byte("bar"), + Payload: []byte("bar"), + Time: time.Unix(20, 0), + Expect: mustHexDecode("92a6ad677e839d3003672cb2cefc71ef80d42e8cb300fefce0807e8398c74068"), + }, + { + Name: "third message", + Headers: []byte("baz"), + Payload: []byte("baz"), + Time: time.Unix(30, 0), + Expect: mustHexDecode("46d6660bd40f2ff5d6246a347895cabe7f4e89d9e1f4c6dad6dabdc801923be1"), + }, + { + Name: "end of stream", + Headers: []byte{}, + Payload: []byte{}, + Time: time.Unix(30, 0), + Expect: mustHexDecode("492bcb3e21de447c85f8a9bef6c0eb833e992e405b9b3584be84edaccc5aad18"), + }, + } + + for _, tt := range messages { + t.Run(tt.Name, func(t *testing.T) { + s, err := signer.SignMessage(tt.Headers, tt.Payload, tt.Time) + if err != nil { + t.Fatal(err) + } + + expect := hex.EncodeToString(tt.Expect) + actual := hex.EncodeToString(s) + if expect != actual { + t.Errorf("%v != %v", expect, actual) + } + }) + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/AbstractDirectedCodegen.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/AbstractDirectedCodegen.java index 68f9e7e6a..820744b4a 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/AbstractDirectedCodegen.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/AbstractDirectedCodegen.java @@ -89,12 +89,9 @@ public void generateStructure(GenerateStructureDirective new StructureGenerator( - directive.model(), - directive.symbolProvider(), + directive.context(), writer, - directive.service(), directive.shape(), - directive.symbolProvider().toSymbol(directive.shape()), null ).run() ); @@ -105,12 +102,9 @@ public void generateError(GenerateErrorDirective d var delegator = directive.context().writerDelegator(); delegator.useShapeWriter(directive.shape(), writer -> new StructureGenerator( - directive.model(), - directive.symbolProvider(), + directive.context(), writer, - directive.service(), directive.shape(), - directive.symbolProvider().toSymbol(directive.shape()), null ).run() ); @@ -120,7 +114,7 @@ public void generateError(GenerateErrorDirective d public void generateUnion(GenerateUnionDirective directive) { var delegator = directive.context().writerDelegator(); delegator.useShapeWriter(directive.shape(), writer -> - new UnionGenerator(directive.model(), directive.symbolProvider(), directive.shape()) + new UnionGenerator(directive.context(), directive.model(), directive.symbolProvider(), directive.shape()) .generateUnion(writer) ); } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ChainWritable.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ChainWritable.java index 1cd7ec7e7..4055cb818 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ChainWritable.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ChainWritable.java @@ -11,6 +11,8 @@ * Chains together multiple Writables that can be composed into one Writable. */ public final class ChainWritable { + // FUTURE: move statics to Writable, make this file- or package-private + private final List writables; public ChainWritable() { @@ -29,6 +31,14 @@ public static ChainWritable of(Collection writables) { return chain; } + public static ChainWritable of(Collection values, Function mapper) { + var chain = new ChainWritable(); + chain.writables.addAll(values.stream() + .map(mapper) + .toList()); + return chain; + } + public boolean isEmpty() { return writables.isEmpty(); } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ClientOptions.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ClientOptions.java index 864bdfb37..42092aaed 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ClientOptions.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ClientOptions.java @@ -15,6 +15,7 @@ package software.amazon.smithy.go.codegen; +import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goDocTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; @@ -40,13 +41,15 @@ public class ClientOptions implements Writable { public static final String NAME = "Options"; + private final GoCodegenContext ctx; private final ProtocolGenerator.GenerationContext context; private final ApplicationProtocol protocol; private final List fields; private final Map authSchemes; - public ClientOptions(ProtocolGenerator.GenerationContext context, ApplicationProtocol protocol) { + public ClientOptions(GoCodegenContext ctx, ProtocolGenerator.GenerationContext context, ApplicationProtocol protocol) { + this.ctx = ctx; this.context = context; this.protocol = protocol; @@ -82,6 +85,8 @@ private Writable generate() { $fields:W + $schemaSerdeProtocolFields:W + $protocolFields:W } @@ -100,10 +105,21 @@ private Writable generate() { "protocolFields", generateProtocolFields(), "copy", generateCopy(), "getIdentityResolver", generateGetIdentityResolver(), - "helpers", generateHelpers() + "helpers", generateHelpers(), + "schemaSerdeProtocolFields", !ctx.settings().useLegacySerde() + ? generateSchemaSerdeProtocolFields() + : emptyGoTemplate() )); } + private Writable generateSchemaSerdeProtocolFields() { + ensureSupportedProtocol(); + return goTemplate(""" + $D + Protocol smithyhttp.ClientProtocol + """, SmithyGoDependency.SMITHY_HTTP_TRANSPORT); + } + private Writable generateProtocolTypes() { ensureSupportedProtocol(); return goTemplate(""" diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java index 201459f00..820df3e64 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java @@ -19,6 +19,7 @@ import java.util.Collection; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -29,18 +30,34 @@ import java.util.stream.Collectors; import software.amazon.smithy.build.FileManifest; import software.amazon.smithy.build.PluginContext; -import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolDependency; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoSettings.ArtifactType; +import software.amazon.smithy.go.codegen.auth.AuthGenerator; +import software.amazon.smithy.go.codegen.endpoints.EndpointResolutionGenerator; +import software.amazon.smithy.go.codegen.endpoints.FnGenerator; +import software.amazon.smithy.go.codegen.endpoints.FnProvider; import software.amazon.smithy.go.codegen.integration.GoIntegration; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.go.codegen.integration.RuntimeClientPlugin; +import software.amazon.smithy.go.codegen.serde2.ListDeserializer; +import software.amazon.smithy.go.codegen.serde2.ListSerializer; +import software.amazon.smithy.go.codegen.serde2.MapDeserializer; +import software.amazon.smithy.go.codegen.serde2.MapSerializer; +import software.amazon.smithy.go.codegen.serde2.Serde2DeserializeResponseMiddleware; +import software.amazon.smithy.go.codegen.serde2.Serde2EventStreamMiddleware; +import software.amazon.smithy.go.codegen.serde2.Serde2SerializeRequestMiddleware; +import software.amazon.smithy.go.codegen.serde2.UnionDeserializer; +import software.amazon.smithy.go.codegen.serde2.UnionSerializer; +import software.amazon.smithy.go.codegen.util.ShapeUtil; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.knowledge.EventStreamIndex; import software.amazon.smithy.model.knowledge.ServiceIndex; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.neighbor.Walker; import software.amazon.smithy.model.shapes.IntEnumShape; +import software.amazon.smithy.model.shapes.ListShape; +import software.amazon.smithy.model.shapes.MapShape; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.Shape; @@ -50,9 +67,11 @@ import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.shapes.UnionShape; import software.amazon.smithy.model.traits.EnumTrait; +import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.model.transform.ModelTransformer; import software.amazon.smithy.utils.OptionalUtils; import software.amazon.smithy.utils.SmithyInternalApi; +import software.amazon.smithy.utils.StringUtils; /** * Orchestrates Go client generation. @@ -219,7 +238,11 @@ void execute() { TopDownIndex.of(model).getContainedOperations(service) .forEach(eventStreamGenerator::generateOperationEventStreamStructure); - if (protocolGenerator != null) { + // All of these things will completely go away when serde2 is done. + // + // There is a block further down after the serde2 stuff that also uses the protocol generator, but that all will + // have to be pulled out of ProtocolGenerator and just be generic. + if (settings.useLegacySerde() && protocolGenerator != null) { LOGGER.info("Generating serde for protocol " + protocolGenerator.getProtocol() + " on " + service.getId()); ProtocolGenerator.GenerationContext.Builder contextBuilder = ProtocolGenerator.GenerationContext.builder() .protocolName(protocolGenerator.getProtocolName()) @@ -251,25 +274,146 @@ void execute() { }); } - writers.useFileWriter("endpoints.go", settings.getModuleName(), writer -> { - ProtocolGenerator.GenerationContext context = contextBuilder.writer(writer).build(); - protocolGenerator.generateEndpointResolution(context); - }); + protocolDocumentGenerator.generateInternalDocumentTypes(protocolGenerator, contextBuilder.build()); + } - writers.useFileWriter("auth.go", settings.getModuleName(), writer -> { - ProtocolGenerator.GenerationContext context = contextBuilder.writer(writer).build(); - protocolGenerator.generateAuth(context); - }); + LOGGER.info("Generating protocol tests for " + service.getId()); + ProtocolUtils.generateHttpProtocolTests(ctx); - writers.useFileWriter("endpoints_test.go", settings.getModuleName(), writer -> { - ProtocolGenerator.GenerationContext context = contextBuilder.writer(writer).build(); - protocolGenerator.generateEndpointResolutionTests(context); + if (!settings.useLegacySerde()) { + protocolDocumentGenerator.generateLegacyInternalDocumentTypes(ctx); + + var shapes = new ArrayList<>(ctx.serdeShapes()); + if (!shapes.contains(ShapeUtil.UNIT)) { + shapes.add(ShapeUtil.UNIT); // targeted by enum members, just generate a blank schema for it + } + + ctx.writerDelegator().useFileWriter("type_registry.go", settings.getModuleName(), new TypeRegistry(ctx)); + + ctx.writerDelegator().useFileWriter("schemas/schemas.go", settings.getModuleName() + "/schemas", writer -> { + var operations = TopDownIndex.of(model).getContainedOperations(service); + for (OperationShape op : operations) { + new SchemaGenerator(ctx, op).accept(writer); + } + + // Generate the service schema: first the underlying *Schema + // (which carries traits like xmlNamespace), then wrap it in + // NewServiceSchema with the service version. + new SchemaGenerator(ctx, service).accept(writer); + writer.addUseImports(SmithyGoDependency.SMITHY); + writer.write("var $L = smithy.NewServiceSchema($L, $S)", + StringUtils.capitalize(service.getId().getName(service)), + SchemaGenerator.getSchemaName(service, service), + service.getVersion()); + writer.write(""); + + // Generate all shape schemas upfront. For synthetic shapes + // (input/output), pass the synthetic itself so that any + // members added by preprocessModel (e.g. presign URL members) + // are included. SchemaGenerator.modelShapeID() resolves the + // archetype for naming. + var resolved = new ArrayList(); + var seen = new HashSet(); + for (Shape s : shapes) { + if (CodegenUtils.isStubSynthetic(s)) { + continue; + } + ShapeId id = s.getId(); + if (id.getNamespace().equals(CodegenUtils.getSyntheticTypeNamespace())) { + id = s.getTrait(Synthetic.class).get().getArchetype().get(); + } + if (seen.add(id)) { + new SchemaGenerator(ctx, s).accept(writer); + resolved.add(s); + } + } + + writer.write(""); + writer.writeDocs("Initialize schema members after all schemas are declared to avoid initialization cycles"); + writer.openBlock("func init() {", "}", () -> { + var shapeIds = new HashSet(); + for (Shape s : resolved) { + shapeIds.add(s.getId()); + } + + var sorted = new ArrayList(); + var visited = new HashSet(); + for (Shape s : resolved) { + topoVisit(s, model, shapeIds, visited, sorted); + } + + for (Shape shape : sorted) { + new SchemaGenerator(ctx, shape).acceptMembersInit(writer); + } + }); }); + var lists = ctx.serdeShapes(ListShape.class); + var maps = ctx.serdeShapes(MapShape.class); + var unionSerdes = ctx.serdeShapes(UnionShape.class).stream() + .filter(it -> !it.hasTrait(StreamingTrait.class)) + .toList(); + + // unfortunately since we have input/output in the top-level package and nested shapes in types/ we have to + // generate these twice since we don't want to export them + // + // also rn we don't check for what's actually used in either package at all but DCE should take care of that + + ctx.writerDelegator().useFileWriter("common_serde.go", settings.getModuleName(), + Writable.map(unionSerdes, it -> new UnionSerializer(ctx, it), true)); + ctx.writerDelegator().useFileWriter("types/common_serde.go", settings.getModuleName() + "/types", + Writable.map(unionSerdes, it -> new UnionSerializer(ctx, it), true)); + ctx.writerDelegator().useFileWriter("common_serde.go", settings.getModuleName(), + Writable.map(unionSerdes, it -> new UnionDeserializer(ctx, it), true)); + ctx.writerDelegator().useFileWriter("types/common_serde.go", settings.getModuleName() + "/types", + Writable.map(unionSerdes, it -> new UnionDeserializer(ctx, it), true)); + + ctx.writerDelegator().useFileWriter("common_serde.go", settings.getModuleName(), + Writable.map(lists, it -> new ListSerializer(ctx, it), true)); + ctx.writerDelegator().useFileWriter("types/common_serde.go", settings.getModuleName() + "/types", + Writable.map(lists, it -> new ListSerializer(ctx, it), true)); + ctx.writerDelegator().useFileWriter("common_serde.go", settings.getModuleName(), + Writable.map(lists, it -> new ListDeserializer(ctx, it), true)); + ctx.writerDelegator().useFileWriter("types/common_serde.go", settings.getModuleName() + "/types", + Writable.map(lists, it -> new ListDeserializer(ctx, it), true)); + + ctx.writerDelegator().useFileWriter("common_serde.go", settings.getModuleName(), + Writable.map(maps, it -> new MapSerializer(ctx, it), true)); + ctx.writerDelegator().useFileWriter("types/common_serde.go", settings.getModuleName() + "/types", + Writable.map(maps, it -> new MapSerializer(ctx, it), true)); + ctx.writerDelegator().useFileWriter("common_serde.go", settings.getModuleName(), + Writable.map(maps, it -> new MapDeserializer(ctx, it), true)); + ctx.writerDelegator().useFileWriter("types/common_serde.go", settings.getModuleName() + "/types", + Writable.map(maps, it -> new MapDeserializer(ctx, it), true)); - LOGGER.info("Generating protocol tests for " + service.getId()); - ProtocolUtils.generateHttpProtocolTests(ctx); + if (eventStreamGenerator.hasEventStreamOperations()) { + var streamIndex = EventStreamIndex.of(model); + eventStreamGenerator.writeEventStreamImplementation(writer -> { + writer.addImport(settings.getModuleName() + "/schemas", "schemas"); + TopDownIndex.of(model).getContainedOperations(service).stream() + .filter(op -> streamIndex.getInputInfo(op).isPresent() + || streamIndex.getOutputInfo(op).isPresent()) + .sorted() + .forEach(op -> writer.write(new Serde2EventStreamMiddleware(ctx, op))); + }); + } + } - protocolDocumentGenerator.generateInternalDocumentTypes(protocolGenerator, contextBuilder.build()); + { + FnProvider fnProvider = integrations.stream() + .map(GoIntegration::getEndpointFnProvider) + .filter(java.util.Objects::nonNull) + .findFirst() + .orElse(new FnGenerator.DefaultFnProvider()); + var endpointGenerator = new EndpointResolutionGenerator(fnProvider); + writers.useFileWriter("endpoints.go", settings.getModuleName(), writer -> { + endpointGenerator.generate(ctx, writer); + }); + writers.useFileWriter("endpoints_test.go", settings.getModuleName(), writer -> { + endpointGenerator.generateTests(ctx, writer); + }); + writers.useFileWriter("auth.go", settings.getModuleName(), writer -> { + new AuthGenerator(ctx, writer).generate(); + }); } LOGGER.fine("Flushing go writers"); @@ -297,9 +441,9 @@ public Void structureShape(StructureShape shape) { if (shape.getId().getNamespace().equals(CodegenUtils.getSyntheticTypeNamespace())) { return null; } - Symbol symbol = symbolProvider.toSymbol(shape); - writers.useShapeWriter(shape, writer -> new StructureGenerator( - model, symbolProvider, writer, service, shape, symbol, protocolGenerator).run()); + writers.useShapeWriter(shape, writer -> + new StructureGenerator(ctx, writer, shape, protocolGenerator).run()); + return null; } @@ -313,9 +457,10 @@ public Void stringShape(StringShape shape) { @Override public Void unionShape(UnionShape shape) { - UnionGenerator generator = new UnionGenerator(model, symbolProvider, shape); + UnionGenerator generator = new UnionGenerator(ctx, model, symbolProvider, shape); writers.useShapeWriter(shape, generator::generateUnion); writers.useShapeExportedTestWriter(shape, generator::generateUnionExamples); + return null; } @@ -366,7 +511,12 @@ public Void serviceShape(ServiceShape shape) { } }); - var clientOptions = new ClientOptions(context, protocol); + if (!ctx.settings().useLegacySerde()) { + writers.useShapeWriter(shape, new Serde2SerializeRequestMiddleware()); + writers.useShapeWriter(shape, new Serde2DeserializeResponseMiddleware()); + } + + var clientOptions = new ClientOptions(ctx, context, protocol); writers.useFileWriter("options.go", settings.getModuleName(), clientOptions); return null; } @@ -376,4 +526,24 @@ public Void intEnumShape(IntEnumShape shape) { writers.useShapeWriter(shape, writer -> new IntEnumGenerator(symbolProvider, writer, shape).run()); return null; } + + private static void topoVisit( + Shape shape, + Model model, + Set inSet, + Set visited, + List result + ) { + if (visited.contains(shape.getId())) { + return; + } + visited.add(shape.getId()); + for (var member : shape.members()) { + var targetId = member.getTarget(); + if (inSet.contains(targetId)) { + topoVisit(model.expectShape(targetId), model, inSet, visited, result); + } + } + result.add(shape); + } } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/DefaultTraitGenerators.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/DefaultTraitGenerators.java new file mode 100644 index 000000000..13693d657 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/DefaultTraitGenerators.java @@ -0,0 +1,102 @@ +package software.amazon.smithy.go.codegen; + +import static software.amazon.smithy.go.codegen.SmithyGoDependency.SMITHY_TRAITS; + +import java.util.HashMap; +import java.util.Map; +import software.amazon.smithy.aws.traits.protocols.AwsQueryCompatibleTrait; +import software.amazon.smithy.aws.traits.protocols.AwsQueryErrorTrait; +import software.amazon.smithy.aws.traits.protocols.Ec2QueryNameTrait; +import software.amazon.smithy.go.codegen.trait.BackfilledInputOutputTrait; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.EventHeaderTrait; +import software.amazon.smithy.model.traits.EventPayloadTrait; +import software.amazon.smithy.model.traits.HostLabelTrait; +import software.amazon.smithy.model.traits.HttpErrorTrait; +import software.amazon.smithy.model.traits.HttpHeaderTrait; +import software.amazon.smithy.model.traits.HttpLabelTrait; +import software.amazon.smithy.model.traits.HttpPayloadTrait; +import software.amazon.smithy.model.traits.HttpPrefixHeadersTrait; +import software.amazon.smithy.model.traits.HttpQueryParamsTrait; +import software.amazon.smithy.model.traits.HttpQueryTrait; +import software.amazon.smithy.model.traits.HttpResponseCodeTrait; +import software.amazon.smithy.model.traits.HttpTrait; +import software.amazon.smithy.model.traits.JsonNameTrait; +import software.amazon.smithy.model.traits.MediaTypeTrait; +import software.amazon.smithy.model.traits.SensitiveTrait; +import software.amazon.smithy.model.traits.StreamingTrait; +import software.amazon.smithy.model.traits.TimestampFormatTrait; +import software.amazon.smithy.model.traits.XmlAttributeTrait; +import software.amazon.smithy.model.traits.XmlFlattenedTrait; +import software.amazon.smithy.model.traits.XmlNameTrait; +import software.amazon.smithy.model.traits.XmlNamespaceTrait; +import software.amazon.smithy.rulesengine.traits.ContextParamTrait; + +public class DefaultTraitGenerators { + private static final Map GENERATORS = new HashMap<>(); + + static { + // Documentation traits + GENERATORS.put(SensitiveTrait.ID, new SimpleTraitGenerator(SMITHY_TRAITS.struct("Sensitive"))); + + // Serialization and Protocol traits + GENERATORS.put(JsonNameTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("JSONName"), + "Name", JsonNameTrait::getValue)); + GENERATORS.put(MediaTypeTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("MediaType"), + "Type", MediaTypeTrait::getValue)); + GENERATORS.put(TimestampFormatTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("TimestampFormat"), + "Format", TimestampFormatTrait::getValue)); + GENERATORS.put(XmlAttributeTrait.ID, new SimpleTraitGenerator(SMITHY_TRAITS.struct("XMLAttribute"))); + GENERATORS.put(XmlFlattenedTrait.ID, new SimpleTraitGenerator(SMITHY_TRAITS.struct("XMLFlattened"))); + GENERATORS.put(XmlNameTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("XMLName"), + "Name", XmlNameTrait::getValue)); + GENERATORS.put(XmlNamespaceTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("XMLNamespace"), + "URI", XmlNamespaceTrait::getUri, + "Prefix", XmlNamespaceTrait::getPrefix)); + + // Streaming + GENERATORS.put(EventHeaderTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("EventHeader"))); + GENERATORS.put(EventPayloadTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("EventPayload"))); + GENERATORS.put(StreamingTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("Streaming"))); + + // HTTP bindings + GENERATORS.put(HttpTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("HTTP"), + "Method", t -> ((HttpTrait) t).getMethod(), + "URI", t -> ((HttpTrait) t).getUri().toString(), + "Code", t -> ((HttpTrait) t).getCode())); + GENERATORS.put(HttpHeaderTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("HTTPHeader"), + "Name", HttpHeaderTrait::getValue)); + GENERATORS.put(HttpLabelTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("HTTPLabel"))); + GENERATORS.put(HttpPayloadTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("HTTPPayload"))); + GENERATORS.put(HttpPrefixHeadersTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("HTTPPrefixHeaders"), + "Prefix", HttpPrefixHeadersTrait::getValue)); + GENERATORS.put(HttpQueryTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("HTTPQuery"), + "Name", HttpQueryTrait::getValue)); + GENERATORS.put(HttpQueryParamsTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("HTTPQueryParams"))); + GENERATORS.put(HttpResponseCodeTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("HTTPResponseCode"))); + GENERATORS.put(HttpErrorTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("HTTPError"), + "Code", HttpErrorTrait::getCode)); + + // Endpoint Traits + GENERATORS.put(HostLabelTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("HostLabel"))); + + // Other traits + GENERATORS.put(ContextParamTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("ContextParam"))); + GENERATORS.put(AwsQueryErrorTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("AWSQueryError"), + "ErrorCode", AwsQueryErrorTrait::getCode, + "StatusCode", AwsQueryErrorTrait::getHttpResponseCode)); + GENERATORS.put(AwsQueryCompatibleTrait.ID, new SimpleTraitGenerator(SMITHY_TRAITS.struct("AWSQueryCompatible"))); + GENERATORS.put(Ec2QueryNameTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("EC2QueryName"), + "Name", Ec2QueryNameTrait::getValue)); + + // Codegen-internal traits + GENERATORS.put(BackfilledInputOutputTrait.ID, new SimpleTraitGenerator<>(SMITHY_TRAITS.struct("UnitShape"))); + } + + public static TraitGenerator forTrait(ShapeId id) { + return GENERATORS.get(id); + } + + private DefaultTraitGenerators() { + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/EventStreamGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/EventStreamGenerator.java index 974ec6a9b..4bfa4cb84 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/EventStreamGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/EventStreamGenerator.java @@ -15,9 +15,13 @@ package software.amazon.smithy.go.codegen; +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.function.Consumer; +import java.util.stream.Collectors; import java.util.stream.Stream; import software.amazon.smithy.codegen.core.Symbol; @@ -26,13 +30,17 @@ import software.amazon.smithy.model.knowledge.EventStreamIndex; import software.amazon.smithy.model.knowledge.OperationIndex; import software.amazon.smithy.model.knowledge.TopDownIndex; +import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.shapes.ToShapeId; +import software.amazon.smithy.model.shapes.UnionShape; +import software.amazon.smithy.model.traits.ErrorTrait; import software.amazon.smithy.model.traits.StreamingTrait; +import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.StringUtils; public final class EventStreamGenerator { @@ -131,9 +139,164 @@ public void generateEventStreamInterfaces() { writer.write("Err() error"); }); }); + + if (!settings.useLegacySerde()) { + inputEvents.forEach(shapeId -> { + var union = model.expectShape(shapeId, UnionShape.class); + generateWriterAdapter(writer, union); + }); + outputEvents.forEach(shapeId -> { + var union = model.expectShape(shapeId, UnionShape.class); + generateReaderAdapter(writer, union); + }); + } }); } + private void generateWriterAdapter(GoWriter writer, UnionShape union) { + var unionSymbol = symbolProvider.toSymbol(union); + var implName = StringUtils.uncapitalize(union.getId().getName(serviceShape)) + "Writer"; + var ifaceName = getEventStreamWriterInterfaceName(serviceShape, union); + var members = union.members().stream() + .filter(m -> !m.getMemberTrait(model, ErrorTrait.class).isPresent()) + .sorted() + .toList(); + + writer.addImport(settings.getModuleName() + "/schemas", "schemas"); + writer.writeGoTemplate(""" + type $impl:L struct { + writer $esWriter:P + } + + var _ $iface:L = (*$impl:L)(nil) + + func (w *$impl:L) Send(ctx $context:T, event $union:T) error { + var variant $schema:P + switch event.(type) { + $cases:W + default: + return $fmtErrorf:T("unknown event type: %T", event) + } + sv, ok := event.($serializable:T) + if !ok { + return $fmtErrorf:T("event %T is not serializable", event) + } + return w.writer.Send(ctx, variant, sv) + } + + func (w *$impl:L) Close() error { + return w.writer.Close() + } + + func (w *$impl:L) Err() error { + return w.writer.Err() + } + """, + MapUtils.of( + "impl", implName, + "iface", ifaceName, + "esWriter", SmithyGoDependency.SMITHY_HTTP_TRANSPORT.pointableSymbol("EventStreamWriter"), + "context", SymbolUtils.createValueSymbolBuilder("Context", SmithyGoDependency.CONTEXT).build(), + "union", unionSymbol, + "schema", SmithyGoDependency.SMITHY.pointableSymbol("Schema"), + "serializable", SmithyGoDependency.SMITHY.valueSymbol("Serializable"), + "fmtErrorf", SymbolUtils.createValueSymbolBuilder("Errorf", SmithyGoDependency.FMT).build(), + "cases", (Writable) (GoWriter w) -> { + for (var member : members) { + var variantSymbol = SymbolUtils.createPointableSymbolBuilder( + symbolProvider.toMemberName(member), + unionSymbol.getNamespace()).build(); + var schemaName = SchemaGenerator.getMemberSchemaName(union, member, serviceShape); + w.write("case $P:", variantSymbol); + w.write(" variant = schemas.$L", schemaName); + } + } + )); + } + + private void generateReaderAdapter(GoWriter writer, UnionShape union) { + var unionSymbol = symbolProvider.toSymbol(union); + var implName = StringUtils.uncapitalize(union.getId().getName(serviceShape)) + "Reader"; + var ifaceName = getEventStreamReaderInterfaceName(serviceShape, union); + var members = union.members().stream() + .filter(m -> !m.getMemberTrait(model, ErrorTrait.class).isPresent()) + .sorted() + .toList(); + + writer.writeGoTemplate(""" + type $impl:L struct { + reader $esReader:P + ch chan $union:T + done chan struct{} + } + + var _ $iface:L = (*$impl:L)(nil) + + func $newImpl:L(reader $esReader:P) *$impl:L { + r := &$impl:L{ + reader: reader, + ch: make(chan $union:T), + done: make(chan struct{}), + } + go r.pipe() + return r + } + + func (r *$impl:L) pipe() { + defer close(r.ch) + for event := range r.reader.Events() { + var ev $union:T + switch v := event.(type) { + $cases:W + default: + continue + } + select { + case r.ch <- ev: + case <-r.done: + return + } + } + } + + func (r *$impl:L) Events() <-chan $union:T { + return r.ch + } + + func (r *$impl:L) Close() error { + close(r.done) + return r.reader.Close() + } + + func (r *$impl:L) Err() error { + return r.reader.Err() + } + """, + MapUtils.of( + "impl", implName, + "iface", ifaceName, + "newImpl", "new" + StringUtils.capitalize(implName), + "esReader", SmithyGoDependency.SMITHY_HTTP_TRANSPORT.pointableSymbol("EventStreamReader"), + "union", unionSymbol, + "cases", (Writable) (GoWriter w) -> { + for (var member : members) { + var targetSymbol = symbolProvider.toSymbol(model.expectShape(member.getTarget())); + var variantSymbol = SymbolUtils.createPointableSymbolBuilder( + symbolProvider.toMemberName(member), + unionSymbol.getNamespace()).build(); + w.write("case $P:", targetSymbol); + w.write(" ev = &$T{Value: *v}", variantSymbol); + } + var esUnknown = SmithyGoDependency.SMITHY_EVENTSTREAM + .pointableSymbol("UnknownUnionMember"); + var typesUnknown = SymbolUtils.createPointableSymbolBuilder( + "UnknownUnionMember", unionSymbol.getNamespace()).build(); + w.write("case $P:", esUnknown); + w.write(" ev = &$T{Tag: v.Tag, Value: v.Value}", typesUnknown); + } + )); + } + public boolean hasEventStreamOperations() { return hasEventStreamOperations(model, serviceShape, streamIndex); } @@ -469,4 +632,16 @@ public static String getEventStreamReaderInterfaceName(ServiceShape serviceShape String name = StringUtils.capitalize(shape.toShapeId().getName(serviceShape)); return name + "Reader"; } + + public static String getEventStreamWriterAdapterName(ServiceShape serviceShape, ToShapeId shape) { + return StringUtils.uncapitalize(shape.toShapeId().getName(serviceShape)) + "Writer"; + } + + public static String getEventStreamReaderAdapterName(ServiceShape serviceShape, ToShapeId shape) { + return StringUtils.uncapitalize(shape.toShapeId().getName(serviceShape)) + "Reader"; + } + + public static String getEventStreamReaderAdapterConstructor(ServiceShape serviceShape, ToShapeId shape) { + return "new" + StringUtils.capitalize(shape.toShapeId().getName(serviceShape)) + "Reader"; + } } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoCodegenContext.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoCodegenContext.java index 131fb8369..b842abf54 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoCodegenContext.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoCodegenContext.java @@ -15,6 +15,9 @@ package software.amazon.smithy.go.codegen; +import static java.util.stream.Collectors.toSet; + +import java.util.HashSet; import java.util.List; import software.amazon.smithy.build.FileManifest; import software.amazon.smithy.codegen.core.CodegenContext; @@ -22,6 +25,12 @@ import software.amazon.smithy.codegen.core.WriterDelegator; import software.amazon.smithy.go.codegen.integration.GoIntegration; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.knowledge.TopDownIndex; +import software.amazon.smithy.model.neighbor.Walker; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.ShapeType; +import software.amazon.smithy.model.traits.ErrorTrait; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi @@ -32,4 +41,42 @@ public record GoCodegenContext( FileManifest fileManifest, WriterDelegator writerDelegator, List integrations -) implements CodegenContext {} +) implements CodegenContext { + public ServiceShape service() { + return settings.getService(model); + } + + /** + * Return all **non-member** shapes in the (de)serialization tree of the service. That is, every shape in the tree + * of the input/outputs of all operations, plus all structures with @error. + */ + public List serdeShapes() { + var walker = new Walker(model); + var operations = TopDownIndex.of(model).getContainedOperations(service()); + + var shapes = new HashSet(); + shapes.addAll(operations.stream() + .map(it -> model.expectShape(it.getInputShape())) + .flatMap(it -> walker.walkShapes(it).stream()) + .collect(toSet())); + shapes.addAll(operations.stream() + .map(it -> model.expectShape(it.getOutputShape())) + .flatMap(it -> walker.walkShapes(it).stream()) + .collect(toSet())); + shapes.addAll(model.getStructureShapesWithTrait(ErrorTrait.class).stream() + .flatMap(it -> walker.walkShapes(it).stream()) + .collect(toSet())); + + return shapes.stream() + .sorted() + .filter(it -> it.getType() != ShapeType.MEMBER) + .toList(); + } + + public List serdeShapes(Class clazz) { + return serdeShapes().stream() + .filter(clazz::isInstance) + .map(clazz::cast) + .toList(); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoSettings.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoSettings.java index 7d6ca3201..1b845bf8f 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoSettings.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoSettings.java @@ -55,6 +55,7 @@ public final class GoSettings { private static final String MODULE_DESCRIPTION = "moduleDescription"; private static final String MODULE_VERSION = "moduleVersion"; private static final String GENERATE_GO_MOD = "generateGoMod"; + private static final String USE_LEGACY_SERDE = "useLegacySerde"; private static final String GO_DIRECTIVE = "goDirective"; private ShapeId service; @@ -62,6 +63,7 @@ public final class GoSettings { private String moduleDescription = ""; private String moduleVersion; private Boolean generateGoMod = false; + private Boolean useLegacySerde = false; private String goDirective = GoModuleInfo.DEFAULT_GO_DIRECTIVE; private ShapeId protocol; private ArtifactType artifactType; @@ -86,7 +88,7 @@ public static GoSettings from(ObjectNode config) { public static GoSettings from(ObjectNode config, ArtifactType artifactType) { GoSettings settings = new GoSettings(); config.warnIfAdditionalProperties( - Arrays.asList(SERVICE, MODULE_NAME, MODULE_DESCRIPTION, MODULE_VERSION, GENERATE_GO_MOD, GO_DIRECTIVE)); + Arrays.asList(SERVICE, MODULE_NAME, MODULE_DESCRIPTION, MODULE_VERSION, GENERATE_GO_MOD, USE_LEGACY_SERDE, GO_DIRECTIVE)); settings.setArtifactType(artifactType); settings.setService(config.expectStringMember(SERVICE).expectShapeId()); settings.setModuleName(config.expectStringMember(MODULE_NAME).getValue()); @@ -94,6 +96,7 @@ public static GoSettings from(ObjectNode config, ArtifactType artifactType) { MODULE_DESCRIPTION, settings.getModuleName() + " client")); settings.setModuleVersion(config.getStringMemberOrDefault(MODULE_VERSION, null)); settings.setGenerateGoMod(config.getBooleanMemberOrDefault(GENERATE_GO_MOD, false)); + settings.setUseLegacySerde(config.getBooleanMemberOrDefault(USE_LEGACY_SERDE, false)); settings.setGoDirective(config.getStringMemberOrDefault(GO_DIRECTIVE, GoModuleInfo.DEFAULT_GO_DIRECTIVE)); return settings; } @@ -217,6 +220,14 @@ public void setGenerateGoMod(Boolean generateGoMod) { this.generateGoMod = Objects.requireNonNull(generateGoMod); } + public Boolean useLegacySerde() { + return useLegacySerde; + } + + public void setUseLegacySerde(Boolean value) { + this.useLegacySerde = Objects.requireNonNull(value); + } + /** * Gets the optional Go directive for the module that will be generated. * diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoWriter.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoWriter.java index 8e4e01c0b..9a66f0056 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoWriter.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoWriter.java @@ -24,6 +24,7 @@ import java.util.function.Consumer; import java.util.logging.Logger; import java.util.regex.Pattern; +import java.util.stream.Collectors; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolContainer; @@ -997,5 +998,4 @@ public String apply(Object type, String indent) { return ""; } } - } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/LegacyJsonProtocolDocumentUtils.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/LegacyJsonProtocolDocumentUtils.java new file mode 100644 index 000000000..fffd44cbc --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/LegacyJsonProtocolDocumentUtils.java @@ -0,0 +1,49 @@ +package software.amazon.smithy.go.codegen; + +import software.amazon.smithy.codegen.core.Symbol; +import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; + +// This is copied almost entirely as-is from JsonProtocolDocumentUtils. It is used for schema-serde to preserve the +// legacy APIs and behavior of documents, which at the time of migration, only supported JSON. Clients using +// schema-based serde don't use these, instead they use Value() and UnmarshalDocument() for requests and responses. +public final class LegacyJsonProtocolDocumentUtils { + public static void generateProtocolDocumentMarshalerUnmarshalDocument(GoCodegenContext context, GoWriter writer) { + writer.write("mBytes, err := m.$L()", ProtocolDocumentGenerator.MARSHAL_SMITHY_DOCUMENT_METHOD); + writer.write("if err != nil { return err }").write(""); + writer.write("jDecoder := $T($T(mBytes))", SymbolUtils.createValueSymbolBuilder("NewDecoder", + SmithyGoDependency.JSON).build(), SymbolUtils.createValueSymbolBuilder("NewReader", + SmithyGoDependency.BYTES).build()); + writer.write("jDecoder.UseNumber()").write(""); + + writer.write("var jv interface{}"); + writer.openBlock("if err := jDecoder.Decode(&v); err != nil {", "}", () -> writer.write("return err")) + .write(""); + + Symbol newUnmarshaler = ProtocolDocumentGenerator.Utilities.getInternalDocumentSymbolBuilder( + context.settings(), ProtocolDocumentGenerator.INTERNAL_NEW_DOCUMENT_UNMARSHALER_FUNC) + .build(); + + writer.write("return $T(v).$L(&jv)", newUnmarshaler, + ProtocolDocumentGenerator.UNMARSHAL_SMITHY_DOCUMENT_METHOD); + } + + public static void generateProtocolDocumentMarshalerMarshalDocument(GoCodegenContext context, GoWriter writer) { + Symbol newEncoder = SymbolUtils.createValueSymbolBuilder("NewEncoder", SmithyGoDependency.SMITHY_DOCUMENT_JSON) + .build(); + + writer.write("return $T().Encode(m.value)", newEncoder); + } + + public static void generateProtocolDocumentUnmarshalerUnmarshalDocument(GoCodegenContext context, GoWriter writer) { + Symbol newDecoder = SymbolUtils.createValueSymbolBuilder("NewDecoder", SmithyGoDependency.SMITHY_DOCUMENT_JSON) + .build(); + + writer.write("decoder := $T()", newDecoder); + writer.write("return decoder.DecodeJSONInterface(m.value, v)"); + } + + public static void generateProtocolDocumentUnmarshalerMarshalDocument(GoCodegenContext context, GoWriter writer) { + writer.write("return $T(m.value)", + SymbolUtils.createValueSymbolBuilder("Marshal", SmithyGoDependency.JSON).build()); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/OperationGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/OperationGenerator.java index 0076d3acf..0a608ee42 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/OperationGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/OperationGenerator.java @@ -34,7 +34,6 @@ import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.StructureShape; -import software.amazon.smithy.model.shapes.UnionShape; import software.amazon.smithy.model.traits.DeprecatedTrait; import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.rulesengine.traits.EndpointBddTrait; @@ -54,6 +53,8 @@ public final class OperationGenerator implements Runnable { private final Symbol operationSymbol; private final ProtocolGenerator protocolGenerator; private final List runtimeClientPlugins; + private final StructureShape input; + private final StructureShape output; OperationGenerator( GoCodegenContext ctx, @@ -71,6 +72,8 @@ public final class OperationGenerator implements Runnable { this.operationSymbol = ctx.symbolProvider().toSymbol(operation); this.protocolGenerator = protocolGenerator; this.runtimeClientPlugins = runtimeClientPlugins; + this.input = ctx.model().expectShape(operation.getInputShape(), StructureShape.class); + this.output = ctx.model().expectShape(operation.getOutputShape(), StructureShape.class); } @Override @@ -133,9 +136,8 @@ public void run() { // Write out the input and output structures. These are written out here to prevent naming conflicts with other // shapes in the model. - new StructureGenerator(model, symbolProvider, writer, service, inputShape, inputSymbol, protocolGenerator) - .renderStructure(() -> { - }, true); + new StructureGenerator(ctx, writer, inputShape, protocolGenerator) + .renderStructure(() -> {}, true); var rulesTrait = service.getTrait(EndpointRuleSetTrait.class); var bddTrait = service.getTrait(EndpointBddTrait.class); @@ -149,17 +151,14 @@ public void run() { .build(); StructureShape structOutputShape; - Symbol structOutputSymbol; - + if (EventStreamGenerator.isV2EventStream(model, operation)) { List onlyEventStreamMembers = outputShape.members().stream().filter(member -> StreamingTrait.isEventStream(model, member)).toList(); - structOutputShape = buildShape(onlyEventStreamMembers); - structOutputSymbol = symbolProvider.toSymbol(outputShape); + structOutputShape = withMembers(outputShape, onlyEventStreamMembers); } else { structOutputShape = outputShape; - structOutputSymbol = outputSymbol; } - new StructureGenerator(model, symbolProvider, writer, service, structOutputShape, structOutputSymbol, protocolGenerator) + new StructureGenerator(ctx, writer, structOutputShape, protocolGenerator, symbolProvider.toSymbol(outputShape)) .renderStructure(() -> { if (outputShape.getMemberNames().size() != 0) { writer.write(""); @@ -191,7 +190,7 @@ public void run() { var nonStreamingOutput = outputShape.members().stream().filter(member -> !StreamingTrait.isEventStream(model, member)).toList(); StructureShape initialReplyStruct = outputShape.toBuilder().clearMembers().members(nonStreamingOutput).build(); - new StructureGenerator(model, symbolProvider, writer, service, initialReplyStruct, initialReply, protocolGenerator) + new StructureGenerator(ctx, writer, initialReplyStruct, protocolGenerator, initialReply) .renderStructure(() -> { writer.writeDocs("Metadata pertaining to the operation's result."); writer.write("ResultMetadata $T", metadataSymbol); @@ -278,17 +277,55 @@ private void generateOperationProtocolMiddlewareAdders() { return err }"""); - // Add request serializer middleware - String serializerMiddlewareName = ProtocolGenerator.getSerializeMiddlewareName( - operation.getId(), service, protocolGenerator.getProtocolName()); - writer.write("err = stack.Serialize.Add(&$L{}, middleware.After)", serializerMiddlewareName); - writer.write("if err != nil { return err }"); + if (ctx.settings().useLegacySerde()) { + // Add request serializer middleware + String serializerMiddlewareName = ProtocolGenerator.getSerializeMiddlewareName( + operation.getId(), service, protocolGenerator.getProtocolName()); + writer.write("err = stack.Serialize.Add(&$L{}, middleware.After)", serializerMiddlewareName); + writer.write("if err != nil { return err }"); + + // Adds response deserializer middleware + String deserializerMiddlewareName = ProtocolGenerator.getDeserializeMiddlewareName( + operation.getId(), service, protocolGenerator.getProtocolName()); + writer.write("err = stack.Deserialize.Add(&$L{}, middleware.After)", deserializerMiddlewareName); + writer.write("if err != nil { return err }"); + } else { + writer.addUseImports(SmithyGoDependency.SMITHY); + var opSchemaName = SchemaGenerator.getSchemaRef(operation, service); + var inputSchemaName = CodegenUtils.isStubSynthetic(input) + ? "nil" : SchemaGenerator.getSchemaRef(input, service); + var outputSchemaName = CodegenUtils.isStubSynthetic(output) + ? "nil" : SchemaGenerator.getSchemaRef(output, service); + var opSchema = String.format("smithy.NewOperationSchema(%s, %s, %s)", + opSchemaName, inputSchemaName, outputSchemaName); + writer.write(""" + if err := stack.Serialize.Add(&serializeRequestMiddleware{options: &options, operationSchema: $L}, middleware.After); err != nil { + return err + }""", opSchema); + writer.write(""" + if err := stack.Deserialize.Add(&deserializeResponseMiddleware{options: &options, operationSchema: $L, output: &$T{}}, middleware.After); err != nil { + return err + }""", opSchema, symbolProvider.toSymbol(output)); + + if (Stream.concat(input.members().stream(), output.members().stream()) + .anyMatch(m -> StreamingTrait.isEventStream(model, m))) { - // Adds response deserializer middleware - String deserializerMiddlewareName = ProtocolGenerator.getDeserializeMiddlewareName( - operation.getId(), service, protocolGenerator.getProtocolName()); - writer.write("err = stack.Deserialize.Add(&$L{}, middleware.After)", deserializerMiddlewareName); - writer.write("if err != nil { return err }"); + var isInput = EventStreamIndex.of(model).getInputInfo(operation).isPresent(); + if (isInput) { + writer.addUseImports(SmithyGoDependency.SMITHY_HTTP_TRANSPORT); + writer.write(""" + if err := smithyhttp.AddInitializeStreamWriter(stack); err != nil { + return err + }"""); + } + + var name = String.format("deserializeOpEventStream%s", operation.getId().getName(service)); + writer.write(""" + if err := stack.Deserialize.Insert(&$L{options: &options}, "OperationDeserializer", middleware.Before); err != nil { + return err + }""", name); + } + } // FUTURE: retry middleware should be at the front of finalize, right now it's added by the SDK writer.write(""" @@ -311,8 +348,9 @@ public static String getAddOperationMiddlewareFuncName(Symbol operation) { return String.format("addOperation%sMiddlewares", operation.getName()); } - private StructureShape buildShape(List members) { - var struct = StructureShape.builder().id(ShapeId.from("synthetic#throwaway")); + private StructureShape withMembers(StructureShape shape, List members) { + var struct = StructureShape.builder().id(shape.getId()) + .addTraits(shape.getAllTraits().values()); members.stream().forEach(member -> struct.addMember( MemberShape.builder() .id(struct.getId().withMember(member.getMemberName())) diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ProtocolDocumentGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ProtocolDocumentGenerator.java index d14e180af..867a2d81e 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ProtocolDocumentGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ProtocolDocumentGenerator.java @@ -353,6 +353,62 @@ public void generateInternalDocumentTypes(ProtocolGenerator protocolGenerator, G }); } + // Used for schema-serde, generates legacy JSON-based document support to preserve existing APIs/behavior. + // Copied from above generateInternalDocumentTypes which will eventually be deleted when schema-serde is complete. + public void generateLegacyInternalDocumentTypes(GoCodegenContext ctx) { + if (!this.hasDocumentShapes) { + return; + } + + writeInternalDocumentPackage("document.go", writer -> { + Symbol marshalerSymbol = getInternalDocumentSymbol("documentMarshaler", + true); + Symbol unmarshalerSymbol = getInternalDocumentSymbol("documentUnmarshaler", true); + + Symbol isDocumentInterface = getInternalDocumentSymbol(INTERNAL_IS_DOCUMENT_INTERFACE); + + writeInternalDocumentImplementation( + writer, + marshalerSymbol, + () -> LegacyJsonProtocolDocumentUtils.generateProtocolDocumentMarshalerUnmarshalDocument(ctx, writer), + () -> LegacyJsonProtocolDocumentUtils.generateProtocolDocumentMarshalerMarshalDocument(ctx, writer)); + writeInternalDocumentImplementation(writer, + unmarshalerSymbol, + () -> LegacyJsonProtocolDocumentUtils.generateProtocolDocumentUnmarshalerUnmarshalDocument(ctx, writer), + () -> LegacyJsonProtocolDocumentUtils.generateProtocolDocumentUnmarshalerMarshalDocument(ctx, writer)); + + Symbol documentInterfaceSymbol = getInternalDocumentSymbol(DOCUMENT_INTERFACE_NAME); + + writer.writeDocs(String.format("%s creates a new document marshaler for the given input type", + INTERNAL_NEW_DOCUMENT_MARSHALER_FUNC)); + writer.openBlock("func $L(v interface{}) $T {", "}", INTERNAL_NEW_DOCUMENT_MARSHALER_FUNC, + documentInterfaceSymbol, () -> { + writer.openBlock("return &$T{", "}", marshalerSymbol, () -> { + writer.write("value: v,"); + }); + }).write(""); + + writer.writeDocs(String.format("%s creates a new document unmarshaler for the given service response", + INTERNAL_NEW_DOCUMENT_UNMARSHALER_FUNC)); + writer.openBlock("func $L(v interface{}) $T {", "}", INTERNAL_NEW_DOCUMENT_UNMARSHALER_FUNC, + documentInterfaceSymbol, () -> { + writer.openBlock("return &$T{", "}", unmarshalerSymbol, () -> { + writer.write("value: v,"); + }); + }).write(""); + + writer.writeDocs(String.format("%s returns whether the given Interface implementation is" + + " a valid client implementation", isDocumentInterface)); + writer.openBlock("func $T(v Interface) (ok bool) {", "}", isDocumentInterface, () -> { + writer.openBlock("defer func() {", "}()", () -> { + writer.openBlock("if err := recover(); err != nil {", "}", () -> writer.write("ok = false")); + }); + writer.write("v.$L()", IS_SMITHY_DOCUMENT_METHOD); + writer.write("return true"); + }).write(""); + }); + } + private void writeInternalDocumentImplementation( GoWriter writer, Symbol typeSymbol, @@ -364,10 +420,13 @@ private void writeInternalDocumentImplementation( }); writer.write(""); + if (!settings.useLegacySerde()) { + writer.write("func (m $P) Value() any { return m.value }", typeSymbol); + } + writer.openBlock("func (m $P) $L(v interface{}) error {", "}", typeSymbol, UNMARSHAL_SMITHY_DOCUMENT_METHOD, unmarshalMethodDefinition); writer.write(""); - writer.openBlock("func (m $P) $L() ([]byte, error) {", "}", typeSymbol, MARSHAL_SMITHY_DOCUMENT_METHOD, marshalMethodDefinition); writer.write(""); diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SchemaGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SchemaGenerator.java new file mode 100644 index 000000000..016d06233 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SchemaGenerator.java @@ -0,0 +1,171 @@ +package software.amazon.smithy.go.codegen; + +import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import java.util.Map; +import java.util.Optional; +import software.amazon.smithy.go.codegen.util.ShapeUtil; +import software.amazon.smithy.model.loader.Prelude; +import software.amazon.smithy.model.shapes.MemberShape; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.Trait; +import software.amazon.smithy.utils.SmithyInternalApi; +import software.amazon.smithy.utils.StringUtils; + +@SmithyInternalApi +public class SchemaGenerator implements Writable { + private final GoCodegenContext ctx; + private final Shape shape; + + public SchemaGenerator(GoCodegenContext ctx, Shape shape) { + this.ctx = ctx; + this.shape = shape; + } + + public static String getSchemaName(Shape shape, ServiceShape service) { + if (Prelude.isPublicPreludeShape(shape)) { + return "smithyprelude." + shape.getId().getName(); + } + var name = modelShapeID(shape).getName(service); + return ShapeUtil.isExported(shape) + ? name + : "_" + name; + } + + public static String getMemberSchemaName(Shape shape, MemberShape member, ServiceShape service) { + return String.format("%s_%s", getSchemaName(shape, service), member.getMemberName()); + } + + // modelShapeID returns the original model shape ID, preferring the + // Synthetic trait's archetype (if any) so that synthetic clones of Input / + // Output inline shapes carry the Smithy model's shape ID rather than the + // Go-codegen synthetic ID. + private static ShapeId modelShapeID(Shape shape) { + Optional synth = shape.getTrait(Synthetic.class); + if (synth.isPresent() && synth.get().getArchetype().isPresent()) { + return synth.get().getArchetype().get(); + } + return shape.getId(); + } + + // Returns the schema reference for use outside the schemas package (e.g. in + // serde generators). Prelude shapes are already package-qualified, local + // shapes get the "schemas." prefix. Stub synthetics (no archetype) return + // "nil" since no schema is generated for them. + public static String getSchemaRef(Shape shape, ServiceShape service) { + if (CodegenUtils.isStubSynthetic(shape)) { + return "nil"; + } + if (Prelude.isPublicPreludeShape(shape)) { + return getSchemaName(shape, service); + } + return "schemas." + getSchemaName(shape, service); + } + + public static String getMemberSchemaRef(Shape shape, MemberShape member, ServiceShape service) { + if (Prelude.isPublicPreludeShape(shape)) { + return getSchemaName(shape, service) + "_" + member.getMemberName(); + } + return "schemas." + getMemberSchemaName(shape, member, service); + } + + @Override + public void accept(GoWriter writer) { + if (Prelude.isPublicPreludeShape(shape)) { + return; + } + var service = ctx.service(); + var modelID = modelShapeID(shape); + writer.addUseImports(SmithyGoDependency.SMITHY); + writer.writeGoTemplate(""" + var $ident:L = smithy.NewSchema(smithy.ShapeID{ + Namespace: $namespace:S, + Name: $name:S, + }, smithy.ShapeType$type:L, $numMembers:L$traits:W) + $memberVarDecls:W + """, + Map.of( + "ident", getSchemaName(shape, service), + "namespace", modelID.getNamespace(), + "name", modelID.getName(), + "type", StringUtils.capitalize(shape.getType().toString()), + "numMembers", shape.members().size(), + "traits", renderVariadicTraits(shape.getAllTraits().values()), + "memberVarDecls", Writable.map(shape.members(), this::renderMemberVarDecl, true) + )); + } + + public void acceptMembersInit(GoWriter writer) { + if (shape.members().isEmpty() || Prelude.isPublicPreludeShape(shape)) { + return; + } + writer.writeGoTemplate(""" + $memberAddAndAssign:W + """, + Map.of( + "memberAddAndAssign", Writable.map(shape.members(), this::renderMemberAddAndAssign, true) + )); + } + + private Writable renderMemberVarDecl(MemberShape member) { + return goTemplate(""" + var $ident:L *smithy.Schema + """, + Map.of("ident", getMemberSchemaName(shape, member, ctx.service()))); + } + + private Writable renderMemberAddAndAssign(MemberShape member) { + var target = ctx.model().expectShape(member.getTarget()); + var service = ctx.service(); + var memberTraits = member.getAllTraits().values(); + + if (Prelude.isPublicPreludeShape(target)) { + return goTemplate(""" + $preludeImport:D$ident:L = $schema:L.AddMember($name:S, $target:L$traits:W) + """, + Map.of( + "preludeImport", SmithyGoDependency.SMITHY_PRELUDE, + "ident", getMemberSchemaName(shape, member, service), + "schema", getSchemaName(shape, service), + "name", member.getMemberName(), + "target", getSchemaName(target, service), + "traits", renderVariadicTraits(memberTraits) + )); + } + + return goTemplate(""" + $ident:L = $schema:L.AddMember($name:S, $target:L$traits:W) + """, + Map.of( + "ident", getMemberSchemaName(shape, member, service), + "schema", getSchemaName(shape, service), + "name", member.getMemberName(), + "target", getSchemaName(target, service), + "traits", renderVariadicTraits(memberTraits) + )); + } + + private Writable renderVariadicTraits(java.util.Collection traits) { + if (traits.isEmpty()) { + return emptyGoTemplate(); + } + return goTemplate(", $W", Writable.map(traits, this::renderVariadicTrait)); + } + + private Writable renderTraitMapEntry(Trait trait) { + var generator = DefaultTraitGenerators.forTrait(trait.toShapeId()); + return generator != null + ? goTemplate("$S: $W,", trait.toShapeId().toString(), generator.render(trait)) + : emptyGoTemplate(); + } + + private Writable renderVariadicTrait(Trait trait) { + var generator = DefaultTraitGenerators.forTrait(trait.toShapeId()); + return generator != null + ? goTemplate("$W,", generator.render(trait)) + : emptyGoTemplate(); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ServiceGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ServiceGenerator.java index 4c93bc7d0..9e2be8473 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ServiceGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ServiceGenerator.java @@ -15,6 +15,7 @@ package software.amazon.smithy.go.codegen; +import static software.amazon.smithy.go.codegen.GoSettings.PROTOCOLS_BY_PRIORITY; import static software.amazon.smithy.go.codegen.GoWriter.autoDocTemplate; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goDocTemplate; @@ -24,8 +25,17 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import software.amazon.smithy.aws.traits.protocols.AwsJson1_0Trait; +import software.amazon.smithy.aws.traits.protocols.AwsJson1_1Trait; +import software.amazon.smithy.aws.traits.protocols.AwsQueryTrait; +import software.amazon.smithy.aws.traits.protocols.Ec2QueryTrait; +import software.amazon.smithy.aws.traits.protocols.RestJson1Trait; +import software.amazon.smithy.aws.traits.protocols.RestXmlTrait; +import software.amazon.smithy.codegen.core.CodegenException; +import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.auth.AuthSchemeResolverGenerator; import software.amazon.smithy.go.codegen.auth.GetIdentityMiddlewareGenerator; @@ -39,13 +49,15 @@ import software.amazon.smithy.go.codegen.integration.GoIntegration; import software.amazon.smithy.go.codegen.integration.OperationMetricsStruct; import software.amazon.smithy.go.codegen.integration.RuntimeClientPlugin; -import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.model.Model; +import software.amazon.smithy.protocol.traits.Rpcv2CborTrait; import software.amazon.smithy.model.knowledge.ServiceIndex; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.protocol.traits.Rpcv2CborTrait; import software.amazon.smithy.utils.MapUtils; +import software.amazon.smithy.utils.StringUtils; /** * Generates a service client, its constructors, and core supporting logic. @@ -196,6 +208,8 @@ func New(options $options:L, optFns ...func(*$options:L)) *$client:L { $protocolResolvers:W + $experimentalSerdeResolvers:W + for _, fn := range optFns { fn(&options) } @@ -243,10 +257,53 @@ func New(options $options:L, optFns ...func(*$options:L)) *$client:L { .flatMap(it -> it.getClientMemberResolvers().stream()) .map(this::generateClientMemberResolver) .toList() - ).compose() + ).compose(), + "experimentalSerdeResolvers", !settings.useLegacySerde()? generateExperimentalSerdeResolvers() : emptyGoTemplate() )); } + private Writable generateExperimentalSerdeResolvers() { + ensureSupportedProtocol(); + return writer -> { + writer.addImport(settings.getModuleName() + "/schemas", "schemas"); + writer.write("options.Protocol = $W", resolveDefaultProtocol()); + }; + } + + private Writable resolveDefaultProtocol() { + Set protocols = ServiceIndex.of(model).getProtocols(service).keySet(); + var preferred = PROTOCOLS_BY_PRIORITY.stream() + .filter(protocols::contains) + .findFirst() + .get(); + + var serviceSchemaRef = "schemas." + StringUtils.capitalize(service.getId().getName(service)); + if (preferred.equals(AwsJson1_0Trait.ID)) { + return goTemplate("$T(" + serviceSchemaRef + ")", + SmithyGoDependency.SMITHY_PROTOCOL_AWSJSON.func("New10")); + } else if (preferred.equals(AwsJson1_1Trait.ID)) { + return goTemplate("$T(" + serviceSchemaRef + ")", + SmithyGoDependency.SMITHY_PROTOCOL_AWSJSON.func("New11")); + } else if (preferred.equals(RestJson1Trait.ID)) { + return goTemplate("$T(" + serviceSchemaRef + ")", + SmithyGoDependency.SMITHY_PROTOCOL_RESTJSON1.func("New")); + } else if (preferred.equals(Rpcv2CborTrait.ID)) { + return goTemplate("$T(" + serviceSchemaRef + ")", + SmithyGoDependency.SMITHY_PROTOCOL_RPCV2.func("NewCBOR")); + } else if (preferred.equals(AwsQueryTrait.ID)) { + return goTemplate("$T(" + serviceSchemaRef + ")", + SmithyGoDependency.SMITHY_PROTOCOL_AWSQUERY.func("New")); + } else if (preferred.equals(Ec2QueryTrait.ID)) { + return goTemplate("$T(" + serviceSchemaRef + ")", + SmithyGoDependency.SMITHY_PROTOCOL_EC2QUERY.func("New")); + } else if (preferred.equals(RestXmlTrait.ID)) { + return goTemplate("$T(" + serviceSchemaRef + ")", + SmithyGoDependency.SMITHY_PROTOCOL_RESTXML.func("New")); + } else { + return goTemplate("nil"); + } + } + private Writable generateGetOptions() { var docs = autoDocTemplate(""" Options returns a copy of the client configuration. diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SimpleTraitGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SimpleTraitGenerator.java new file mode 100644 index 000000000..49accd5a2 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SimpleTraitGenerator.java @@ -0,0 +1,66 @@ +package software.amazon.smithy.go.codegen; + +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import software.amazon.smithy.codegen.core.Symbol; +import software.amazon.smithy.model.traits.Trait; + +class SimpleTraitGenerator implements TraitGenerator { + private final Symbol symbol; + private final Map> mappings = new LinkedHashMap<>(); + + public SimpleTraitGenerator(Symbol symbol) { + this.symbol = symbol; + } + + public SimpleTraitGenerator(Symbol symbol, + String k1, Function v1) { + this.symbol = symbol; + + mappings.put(k1, v1); + } + + public SimpleTraitGenerator(Symbol symbol, + String k1, Function v1, + String k2, Function v2) { + this.symbol = symbol; + + mappings.put(k1, v1); + mappings.put(k2, v2); + } + + public SimpleTraitGenerator(Symbol symbol, + String k1, Function v1, + String k2, Function v2, + String k3, Function v3) { + this.symbol = symbol; + + mappings.put(k1, v1); + mappings.put(k2, v2); + mappings.put(k3, v3); + } + + @Override + public Writable render(Trait trait) { + return goTemplate("&$T{$W}", symbol, Writable.map(mappings.entrySet(), entry -> { + var value = entry.getValue().apply((T) trait); + if (value instanceof Optional opt) { + if (opt.isEmpty()) { + return GoWriter.emptyGoTemplate(); + } + value = opt.get(); + } + return goTemplate("$L: $L,", entry.getKey(), formatValue(value)); + })); + } + + private String formatValue(Object value) { + return value instanceof String + ? "\"" + value + "\"" + : value.toString(); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SmithyGoDependency.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SmithyGoDependency.java index 2f5bbbb80..9e35478b2 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SmithyGoDependency.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SmithyGoDependency.java @@ -51,6 +51,7 @@ public final class SmithyGoDependency { public static final GoDependency SLICES = stdlib("slices"); public static final GoDependency SMITHY = smithy(null, "smithy"); + public static final GoDependency SMITHY_TRAITS = smithy("traits", "smithytraits"); public static final GoDependency SMITHY_TRANSPORT = smithy("transport", "smithytransport"); public static final GoDependency SMITHY_HTTP_TRANSPORT = smithy("transport/http", "smithyhttp"); public static final GoDependency SMITHY_MIDDLEWARE = smithy("middleware"); @@ -69,9 +70,11 @@ public final class SmithyGoDependency { public static final GoDependency SMITHY_TESTING = smithy("testing", "smithytesting"); public static final GoDependency SMITHY_WAITERS = smithy("waiter", "smithywaiter"); public static final GoDependency SMITHY_DOCUMENT = smithy("document", "smithydocument"); + public static final GoDependency SMITHY_PRELUDE = smithy("prelude", "smithyprelude"); public static final GoDependency SMITHY_DOCUMENT_JSON = smithy("document/json", "smithydocumentjson"); public static final GoDependency SMITHY_DOCUMENT_CBOR = smithy("document/cbor", "smithydocumentcbor"); public static final GoDependency SMITHY_SYNC = smithy("sync", "smithysync"); + public static final GoDependency SMITHY_EVENTSTREAM = smithy("eventstream", "eventstream"); public static final GoDependency SMITHY_AUTH = smithy("auth", "smithyauth"); public static final GoDependency SMITHY_AUTH_BEARER = smithy("auth/bearer"); public static final GoDependency SMITHY_ENDPOINTS = smithy("endpoints", "smithyendpoints"); @@ -80,6 +83,13 @@ public final class SmithyGoDependency { public static final GoDependency SMITHY_TRACING = smithy("tracing"); public static final GoDependency SMITHY_METRICS = smithy("metrics"); + public static final GoDependency SMITHY_PROTOCOL_AWSJSON = smithy("transport/http/protocol/awsjson"); + public static final GoDependency SMITHY_PROTOCOL_RESTJSON1 = smithy("transport/http/protocol/restjson1"); + public static final GoDependency SMITHY_PROTOCOL_AWSQUERY = smithy("transport/http/protocol/awsquery"); + public static final GoDependency SMITHY_PROTOCOL_EC2QUERY = smithy("transport/http/protocol/ec2query"); + public static final GoDependency SMITHY_PROTOCOL_RESTXML = smithy("transport/http/protocol/restxml"); + public static final GoDependency SMITHY_PROTOCOL_RPCV2 = smithy("transport/http/protocol/rpcv2"); + public static final GoDependency MATH = stdlib("math"); private static final String SMITHY_SOURCE_PATH = "github.com/aws/smithy-go"; diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/StructureGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/StructureGenerator.java index bd54b210f..d5035b9d5 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/StructureGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/StructureGenerator.java @@ -16,15 +16,22 @@ package software.amazon.smithy.go.codegen; import java.util.Map; +import java.util.Optional; import java.util.Set; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; +import software.amazon.smithy.go.codegen.serde2.StructureDeserializer; +import software.amazon.smithy.go.codegen.serde2.StructureSerializer; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.model.shapes.ShapeType; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.traits.ErrorTrait; +import software.amazon.smithy.model.traits.HttpPayloadTrait; +import software.amazon.smithy.model.traits.InputTrait; +import software.amazon.smithy.model.traits.OutputTrait; import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SetUtils; @@ -46,26 +53,38 @@ public final class StructureGenerator implements Runnable { private final SymbolProvider symbolProvider; private final GoWriter writer; private final StructureShape shape; - private final Symbol symbol; + private Symbol symbol; private final ServiceShape service; private final ProtocolGenerator protocolGenerator; + private final boolean useLegacySerde; + private final GoCodegenContext ctx; public StructureGenerator( - Model model, - SymbolProvider symbolProvider, + GoCodegenContext ctx, GoWriter writer, - ServiceShape service, StructureShape shape, - Symbol symbol, ProtocolGenerator protocolGenerator ) { - this.model = model; - this.symbolProvider = symbolProvider; + this.ctx = ctx; + this.model = ctx.model(); + this.symbolProvider = ctx.symbolProvider(); this.writer = writer; - this.service = service; + this.service = ctx.service(); this.shape = shape; - this.symbol = symbol; + this.symbol = ctx.symbolProvider().toSymbol(shape); this.protocolGenerator = protocolGenerator; + this.useLegacySerde = ctx.settings().useLegacySerde(); + } + + public StructureGenerator( + GoCodegenContext ctx, + GoWriter writer, + StructureShape shape, + ProtocolGenerator protocolGenerator, + Symbol symbolOverride + ) { + this(ctx, writer, shape, protocolGenerator); + this.symbol = symbolOverride; } @Override @@ -129,6 +148,42 @@ public void renderStructure(Runnable runnable, boolean isInputStructure) { writer.write("$L", ProtocolDocumentGenerator.NO_DOCUMENT_SERDE_TYPE_NAME); writer.closeBlock("}").write(""); + + // Only generate serde methods when the symbol matches the shape's natural symbol. + // When a symbol override is provided (e.g. for synthetic shapes like event stream + // initial reply structs), skip serde to avoid duplicate method declarations. + if (!useLegacySerde && symbol.getName().equals(ctx.symbolProvider().toSymbol(shape).getName())) { + if (shape.hasTrait(InputTrait.class)) { + writer.write(new StructureSerializer(ctx, shape)); + } else if (shape.hasTrait(OutputTrait.class)) { + writer.write(new StructureDeserializer(ctx, shape)); + } else { + writer.write(new StructureSerializer(ctx, shape)); + writer.write(new StructureDeserializer(ctx, shape)); + } + + getStreamingPayloadMember().ifPresent(member -> { + var memberName = symbolProvider.toMemberName(member); + writer.addUseImports(SmithyGoDependency.SMITHY); + writer.addUseImports(SmithyGoDependency.IO); + writer.write(""" + func (v *$1L) GetPayloadStream() io.Reader { return v.$2L } + var _ smithy.StreamingInput = (*$1L)(nil) + func (v *$1L) SetPayloadStream(r io.ReadCloser) { v.$2L = r } + var _ smithy.StreamingOutput = (*$1L)(nil) + """, symbol.getName(), memberName); + }); + } + } + + private Optional getStreamingPayloadMember() { + return shape.getAllMembers().values().stream() + .filter(m -> m.hasTrait(HttpPayloadTrait.class)) + .filter(m -> { + var target = model.expectShape(m.getTarget()); + return target.getType() == ShapeType.BLOB && target.hasTrait(StreamingTrait.class); + }) + .findFirst(); } /** @@ -190,5 +245,9 @@ private void renderErrorStructure() { fault = "smithy.FaultServer"; } writer.write("func (e *$L) ErrorFault() smithy.ErrorFault { return $L }", structureSymbol.getName(), fault); + + if (!useLegacySerde) { + writer.write(new StructureDeserializer(ctx, shape)); + } } } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/TraitGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/TraitGenerator.java new file mode 100644 index 000000000..b95585d43 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/TraitGenerator.java @@ -0,0 +1,7 @@ +package software.amazon.smithy.go.codegen; + +import software.amazon.smithy.model.traits.Trait; + +public interface TraitGenerator { + Writable render(Trait trait); +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/TypeRegistry.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/TypeRegistry.java new file mode 100644 index 000000000..40d302201 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/TypeRegistry.java @@ -0,0 +1,67 @@ +package software.amazon.smithy.go.codegen; + +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import java.util.HashSet; +import java.util.Map; +import java.util.stream.Collectors; +import software.amazon.smithy.model.knowledge.EventStreamIndex; +import software.amazon.smithy.model.knowledge.TopDownIndex; +import software.amazon.smithy.model.loader.Prelude; +import software.amazon.smithy.model.shapes.StructureShape; +import software.amazon.smithy.model.traits.ErrorTrait; + +public class TypeRegistry implements Writable { + private final GoCodegenContext ctx; + + public TypeRegistry(GoCodegenContext ctx) { + this.ctx = ctx; + } + + @Override + public void accept(GoWriter writer) { + var model = ctx.model(); + var service = ctx.service(); + + // Errors: needed for DeserializableError lookup + var shapes = new HashSet(); + shapes.addAll(model.getStructureShapesWithTrait(ErrorTrait.class)); + + // Event stream events: needed for LookupEntry by target ID + var streamIndex = EventStreamIndex.of(model); + for (var op : TopDownIndex.of(model).getContainedOperations(service)) { + streamIndex.getInputInfo(op).ifPresent(info -> + info.getEventStreamTarget().members().forEach(m -> + shapes.add(model.expectShape(m.getTarget(), StructureShape.class)))); + streamIndex.getOutputInfo(op).ifPresent(info -> + info.getEventStreamTarget().members().forEach(m -> + shapes.add(model.expectShape(m.getTarget(), StructureShape.class)))); + } + + var sorted = shapes.stream().sorted().collect(Collectors.toList()); + + writer.addUseImports(SmithyGoDependency.SMITHY); + if (sorted.stream().anyMatch(Prelude::isPublicPreludeShape)) { + writer.addUseImports(SmithyGoDependency.SMITHY_PRELUDE); + } + if (sorted.stream().anyMatch(s -> !Prelude.isPublicPreludeShape(s))) { + writer.addImport(ctx.settings().getModuleName() + "/schemas", "schemas"); + } + writer.writeGoTemplate(""" + // TypeRegistry is the type registry for this service. + var TypeRegistry = &smithy.TypeRegistry{ + Entries: map[string]*smithy.TypeRegistryEntry{ + $entries:W + }, + } + """, Map.of("entries", Writable.map(sorted, this::renderEntry))); + } + + private Writable renderEntry(StructureShape shape) { + return goTemplate(""" + $S: &smithy.TypeRegistryEntry{ + Schema: $L, + New: func() any { return &$T{} }, + },""", shape.getId().toString(), SchemaGenerator.getSchemaRef(shape, ctx.service()), ctx.symbolProvider().toSymbol(shape)); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/UnionGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/UnionGenerator.java index b3faf9dae..af24c8adf 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/UnionGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/UnionGenerator.java @@ -20,6 +20,7 @@ import java.util.Set; import java.util.TreeSet; import java.util.stream.Collectors; +import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.model.Model; @@ -27,6 +28,7 @@ import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.SimpleShape; import software.amazon.smithy.model.shapes.UnionShape; +import software.amazon.smithy.go.codegen.util.ShapeUtil; import software.amazon.smithy.model.traits.ErrorTrait; import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.utils.SmithyInternalApi; @@ -38,12 +40,14 @@ public class UnionGenerator { public static final String UNKNOWN_MEMBER_NAME = "UnknownUnionMember"; + private final GoCodegenContext ctx; private final Model model; private final SymbolProvider symbolProvider; private final UnionShape shape; private final boolean isEventStream; - public UnionGenerator(Model model, SymbolProvider symbolProvider, UnionShape shape) { + public UnionGenerator(GoCodegenContext ctx, Model model, SymbolProvider symbolProvider, UnionShape shape) { + this.ctx = ctx; this.model = model; this.symbolProvider = symbolProvider; this.shape = shape; @@ -99,8 +103,100 @@ public void generateUnion(GoWriter writer) { }); writer.write("func (*$L) is$L() {}", exportedMemberName, symbol.getName()); + + if (!ctx.settings().useLegacySerde()) { + generateMemberSerializer(writer, member, exportedMemberName, target); + generateMemberDeserializer(writer, member, exportedMemberName, target); + } } } + + private void generateMemberSerializer(GoWriter writer, MemberShape member, String memberName, Shape target) { + writer.addUseImports(SmithyGoDependency.SMITHY); + writer.addImport(ctx.settings().getModuleName() + "/schemas", "schemas"); + var schemaName = SchemaGenerator.getMemberSchemaRef(shape, member, ctx.service()); + writer.openBlock("func (v *$L) Serialize(s smithy.ShapeSerializer) {", "}", memberName, () -> { + switch (target.getType()) { + case BYTE -> writer.write("s.WriteInt8($L, v.Value)", schemaName); + case SHORT -> writer.write("s.WriteInt16($L, v.Value)", schemaName); + case INTEGER -> writer.write("s.WriteInt32($L, v.Value)", schemaName); + case LONG -> writer.write("s.WriteInt64($L, v.Value)", schemaName); + case FLOAT -> writer.write("s.WriteFloat32($L, v.Value)", schemaName); + case DOUBLE -> writer.write("s.WriteFloat64($L, v.Value)", schemaName); + case BOOLEAN -> writer.write("s.WriteBool($L, v.Value)", schemaName); + case STRING, ENUM -> { + if (ShapeUtil.isEnum(target)) { + writer.write("s.WriteString($L, string(v.Value))", schemaName); + } else { + writer.write("s.WriteString($L, v.Value)", schemaName); + } + } + case BLOB -> writer.write("s.WriteBlob($L, v.Value)", schemaName); + case TIMESTAMP -> writer.write("s.WriteTime($L, v.Value)", schemaName); + case INT_ENUM -> writer.write("s.WriteInt32($L, int32(v.Value))", schemaName); + case BIG_INTEGER -> writer.write("s.WriteBigInt($L, v.Value)", schemaName); + case BIG_DECIMAL -> writer.write("s.WriteBigFloat($L, v.Value)", schemaName); + case STRUCTURE -> writer.write("s.WriteStruct($L)\nv.Value.SerializeMembers(s)\ns.CloseStruct()", schemaName); // struct variants are value types + case LIST, SET, MAP -> writer.write("serialize$L(s, $L, v.Value)", target.getId().getName(), schemaName); + case UNION -> writer.write("serialize$L(s, $L, v.Value)", target.getId().getName(), schemaName); + case DOCUMENT -> { + writer.addUseImports(SmithyGoDependency.SMITHY_DOCUMENT); + writer.write("s.WriteDocument($L, &smithydocument.Opaque{Value: v.Value})", schemaName); + } + case MEMBER, SERVICE, RESOURCE, OPERATION -> throw new CodegenException("invalid shape type " + target.getType()); + } + }); + } + + private void generateMemberDeserializer(GoWriter writer, MemberShape member, String memberName, Shape target) { + writer.addUseImports(SmithyGoDependency.SMITHY); + writer.addImport(ctx.settings().getModuleName() + "/schemas", "schemas"); + var schemaName = SchemaGenerator.getMemberSchemaRef(shape, member, ctx.service()); + writer.openBlock("func (v *$L) Deserialize(d smithy.ShapeDeserializer) error {", "}", memberName, () -> { + switch (target.getType()) { + case BYTE -> writer.write("return d.ReadInt8($L, &v.Value)", schemaName); + case SHORT -> writer.write("return d.ReadInt16($L, &v.Value)", schemaName); + case INTEGER -> writer.write("return d.ReadInt32($L, &v.Value)", schemaName); + case LONG -> writer.write("return d.ReadInt64($L, &v.Value)", schemaName); + case FLOAT -> writer.write("return d.ReadFloat32($L, &v.Value)", schemaName); + case DOUBLE -> writer.write("return d.ReadFloat64($L, &v.Value)", schemaName); + case BOOLEAN -> writer.write("return d.ReadBool($L, &v.Value)", schemaName); + case STRING, ENUM -> { + if (ShapeUtil.isEnum(target)) { + writer.write("var s string"); + writer.write("if err := d.ReadString($L, &s); err != nil { return err }", schemaName); + writer.write("v.Value = $T(s)", symbolProvider.toSymbol(target)); + writer.write("return nil"); + } else { + writer.write("return d.ReadString($L, &v.Value)", schemaName); + } + } + case BLOB -> writer.write("return d.ReadBlob($L, &v.Value)", schemaName); + case TIMESTAMP -> writer.write("return d.ReadTime($L, &v.Value)", schemaName); + case INT_ENUM -> { + writer.write("var i int32"); + writer.write("if err := d.ReadInt32($L, &i); err != nil { return err }", schemaName); + writer.write("v.Value = $T(i)", symbolProvider.toSymbol(target)); + writer.write("return nil"); + } + case STRUCTURE -> writer.write("return v.Value.Deserialize(d)"); + case LIST, MAP, UNION -> writer.write("return deserialize$L(d, $L, &v.Value)", target.getId().getName(), schemaName); + case DOCUMENT -> { + var unmarshaler = ProtocolDocumentGenerator.Utilities.getInternalDocumentSymbolBuilder( + ctx.settings(), ProtocolDocumentGenerator.INTERNAL_NEW_DOCUMENT_UNMARSHALER_FUNC).build(); + writer.addUseImports(SmithyGoDependency.SMITHY_DOCUMENT); + writer.write(""" + var dv smithydocument.Value + if err := d.ReadDocument($1L, &dv); err != nil { return err } + if ov, ok := dv.(smithydocument.Opaque); ok { + v.Value = $2T(ov.Value) + } + return nil""", schemaName, unmarshaler); + } + case MEMBER, SERVICE, RESOURCE, OPERATION -> throw new CodegenException("invalid shape type " + target.getType()); + } + }); + } private boolean isEventStreamErrorMember(MemberShape memberShape) { return isEventStream && memberShape.getMemberTrait(model, ErrorTrait.class).isPresent(); diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/UnsupportedShapeException.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/UnsupportedShapeException.java new file mode 100644 index 000000000..abc98135f --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/UnsupportedShapeException.java @@ -0,0 +1,10 @@ +package software.amazon.smithy.go.codegen; + +import software.amazon.smithy.codegen.core.CodegenException; +import software.amazon.smithy.model.shapes.ShapeType; + +public class UnsupportedShapeException extends CodegenException { + public UnsupportedShapeException(ShapeType type) { + super("unsupported shape type: " + type); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/Writable.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/Writable.java index 502bcbe6e..4cc6a3307 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/Writable.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/Writable.java @@ -1,6 +1,18 @@ package software.amazon.smithy.go.codegen; +import java.util.Collection; import java.util.function.Consumer; +import java.util.function.Function; public interface Writable extends Consumer { + static Writable map(Collection items, Function mapper, boolean writeNewlines) { + return ChainWritable.of(items.stream() + .map(mapper) + .toList() + ).compose(writeNewlines); + } + + static Writable map(Collection items, Function mapper) { + return map(items, mapper, false); + } } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthGenerator.java index bc5d4b01e..c396da00a 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthGenerator.java @@ -15,35 +15,25 @@ package software.amazon.smithy.go.codegen.auth; -import software.amazon.smithy.codegen.core.CodegenException; -import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; +import software.amazon.smithy.go.codegen.GoCodegenContext; +import software.amazon.smithy.go.codegen.GoWriter; -/** - * Entry point into smithy client auth generation. - */ public class AuthGenerator { - private final ProtocolGenerator.GenerationContext context; + private final GoCodegenContext ctx; + private final GoWriter writer; - public AuthGenerator(ProtocolGenerator.GenerationContext context) { - this.context = context; + public AuthGenerator(GoCodegenContext ctx, GoWriter writer) { + this.ctx = ctx; + this.writer = writer; } public void generate() { - if (context.getWriter().isEmpty()) { - throw new CodegenException("writer is required"); - } - - context.getWriter().get() - .write("$W\n", new AuthParametersGenerator(context).generate()) - .write("$W\n", new AuthParametersResolverGenerator(context).generate()) - .write("$W\n", getResolverGenerator().generate()) - .write("$W\n", new ResolveAuthSchemeMiddlewareGenerator(context).generate()) - .write("$W\n", new GetIdentityMiddlewareGenerator(context).generate()) - .write("$W\n", new SignRequestMiddlewareGenerator(context).generate()); - } - - // TODO(i&a): allow consuming generators to overwrite - private AuthSchemeResolverGenerator getResolverGenerator() { - return new AuthSchemeResolverGenerator(context); + writer + .write("$W\n", new AuthParametersGenerator(ctx).generate()) + .write("$W\n", new AuthParametersResolverGenerator(ctx).generate()) + .write("$W\n", new AuthSchemeResolverGenerator(ctx).generate()) + .write("$W\n", new ResolveAuthSchemeMiddlewareGenerator().generate()) + .write("$W\n", new GetIdentityMiddlewareGenerator().generate()) + .write("$W\n", new SignRequestMiddlewareGenerator().generate()); } } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthParametersGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthParametersGenerator.java index 0db56e007..08d97c6b8 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthParametersGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthParametersGenerator.java @@ -20,9 +20,9 @@ import java.util.ArrayList; import software.amazon.smithy.codegen.core.Symbol; +import software.amazon.smithy.go.codegen.GoCodegenContext; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.go.codegen.Writable; -import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.utils.ListUtils; import software.amazon.smithy.utils.MapUtils; @@ -37,14 +37,14 @@ public class AuthParametersGenerator { public static final Symbol STRUCT_SYMBOL = SymbolUtils.createPointableSymbolBuilder(STRUCT_NAME).build(); - private final ProtocolGenerator.GenerationContext context; + private final GoCodegenContext ctx; private final ArrayList fields = new ArrayList<>( ListUtils.of(AuthParameter.OPERATION) ); - public AuthParametersGenerator(ProtocolGenerator.GenerationContext context) { - this.context = context; + public AuthParametersGenerator(GoCodegenContext ctx) { + this.ctx = ctx; } public Writable generate() { @@ -88,9 +88,9 @@ private Writable generateFields() { } private void loadFields() { - for (var integration: context.getIntegrations()) { + for (var integration: ctx.integrations()) { var plugins = integration.getClientPlugins().stream().filter(it -> - it.matchesService(context.getModel(), context.getService())).toList(); + it.matchesService(ctx.model(), ctx.service())).toList(); for (var plugin: plugins) { fields.addAll(plugin.getAuthParameters()); } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthParametersResolverGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthParametersResolverGenerator.java index b2959ff98..ffa365b00 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthParametersResolverGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthParametersResolverGenerator.java @@ -18,9 +18,9 @@ import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import java.util.ArrayList; +import software.amazon.smithy.go.codegen.GoCodegenContext; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.Writable; -import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.utils.MapUtils; /** @@ -31,12 +31,12 @@ public class AuthParametersResolverGenerator { public static final String FUNC_NAME = "bindAuthResolverParams"; - private final ProtocolGenerator.GenerationContext context; + private final GoCodegenContext ctx; private final ArrayList resolvers = new ArrayList<>(); - public AuthParametersResolverGenerator(ProtocolGenerator.GenerationContext context) { - this.context = context; + public AuthParametersResolverGenerator(GoCodegenContext ctx) { + this.ctx = ctx; } public Writable generate() { @@ -74,9 +74,9 @@ private Writable generateResolvers() { } private void loadResolvers() { - for (var integration: context.getIntegrations()) { + for (var integration: ctx.integrations()) { var plugins = integration.getClientPlugins().stream().filter(it -> - it.matchesService(context.getModel(), context.getService())).toList(); + it.matchesService(ctx.model(), ctx.service())).toList(); for (var plugin: plugins) { resolvers.addAll(plugin.getAuthParameterResolvers()); } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthSchemeResolverGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthSchemeResolverGenerator.java index c14259a0e..2dea29dd9 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthSchemeResolverGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthSchemeResolverGenerator.java @@ -23,6 +23,7 @@ import java.util.stream.Collectors; import software.amazon.smithy.aws.traits.auth.UnsignedPayloadTrait; import software.amazon.smithy.go.codegen.ChainWritable; +import software.amazon.smithy.go.codegen.GoCodegenContext; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.Writable; import software.amazon.smithy.go.codegen.integration.AuthSchemeDefinition; @@ -41,15 +42,25 @@ public class AuthSchemeResolverGenerator { public static final String INTERFACE_NAME = "AuthSchemeResolver"; public static final String DEFAULT_NAME = "defaultAuthSchemeResolver"; - private final ProtocolGenerator.GenerationContext context; + private final GoCodegenContext ctx; + private final ProtocolGenerator.GenerationContext legacyContext; private final ServiceIndex serviceIndex; private final Map schemeDefinitions; - public AuthSchemeResolverGenerator(ProtocolGenerator.GenerationContext context) { - this.context = context; - this.serviceIndex = ServiceIndex.of(context.getModel()); - this.schemeDefinitions = context.getIntegrations().stream() - .flatMap(it -> it.getClientPlugins(context.getModel(), context.getService()).stream()) + public AuthSchemeResolverGenerator(GoCodegenContext ctx) { + this.ctx = ctx; + this.legacyContext = ProtocolGenerator.GenerationContext.builder() + .protocolName("") + .integrations(ctx.integrations()) + .model(ctx.model()) + .service(ctx.service()) + .settings(ctx.settings()) + .symbolProvider(ctx.symbolProvider()) + .delegator((software.amazon.smithy.go.codegen.GoDelegator) ctx.writerDelegator()) + .build(); + this.serviceIndex = ServiceIndex.of(ctx.model()); + this.schemeDefinitions = ctx.integrations().stream() + .flatMap(it -> it.getClientPlugins(ctx.model(), ctx.service()).stream()) .flatMap(it -> it.getAuthSchemeDefinitions().entrySet().stream()) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } @@ -60,10 +71,10 @@ public AuthSchemeResolverGenerator(ProtocolGenerator.GenerationContext context) // 3. it has an unsigned payload private boolean hasAuthOverrides(OperationShape operation) { var serviceSchemes = serviceIndex - .getEffectiveAuthSchemes(context.getService(), ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE) + .getEffectiveAuthSchemes(ctx.service(), ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE) .keySet(); var operationSchemes = serviceIndex - .getEffectiveAuthSchemes(context.getService(), operation, ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE) + .getEffectiveAuthSchemes(ctx.service(), operation, ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE) .keySet(); return !serviceSchemes.equals(operationSchemes) || operation.hasTrait(UnsignedPayloadTrait.class); } @@ -136,8 +147,8 @@ private Writable generateDefaultResolve() { private Writable generateOperationAuthOptions() { var options = new ChainWritable(); - TopDownIndex.of(context.getModel()) - .getContainedOperations(context.getService()).stream() + TopDownIndex.of(ctx.model()) + .getContainedOperations(ctx.service()).stream() .filter(this::hasAuthOverrides) .forEach(it -> { options.add(generateOperationAuthOptionsEntry(it)); @@ -156,12 +167,12 @@ private Writable generateOperationAuthOptions() { private Writable generateOperationAuthOptionsEntry(OperationShape operation) { var options = new ChainWritable(); serviceIndex - .getEffectiveAuthSchemes(context.getService(), operation, ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE) + .getEffectiveAuthSchemes(ctx.service(), operation, ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE) .entrySet().stream() .filter(it -> schemeDefinitions.containsKey(it.getKey())) .forEach(it -> { var definition = schemeDefinitions.get(it.getKey()); - options.add(definition.generateOperationOption(context, operation)); + options.add(definition.generateOperationOption(legacyContext, operation)); }); return options.isEmpty() @@ -181,12 +192,12 @@ private Writable generateOperationAuthOptionsEntry(OperationShape operation) { private Writable generateServiceAuthOptions() { var options = new ChainWritable(); serviceIndex - .getEffectiveAuthSchemes(context.getService(), ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE) + .getEffectiveAuthSchemes(ctx.service(), ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE) .entrySet().stream() .filter(it -> schemeDefinitions.containsKey(it.getKey())) .forEach(it -> { var definition = schemeDefinitions.get(it.getKey()); - options.add(definition.generateServiceOption(context, context.getService())); + options.add(definition.generateServiceOption(legacyContext, ctx.service())); }); return goTemplate(""" diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/GetIdentityMiddlewareGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/GetIdentityMiddlewareGenerator.java index 10b51a23d..1d7bfb6e4 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/GetIdentityMiddlewareGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/GetIdentityMiddlewareGenerator.java @@ -23,17 +23,13 @@ import software.amazon.smithy.go.codegen.MiddlewareIdentifier; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.Writable; -import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.utils.MapUtils; public class GetIdentityMiddlewareGenerator { public static final String MIDDLEWARE_NAME = "getIdentityMiddleware"; public static final String MIDDLEWARE_ID = "GetIdentity"; - private final ProtocolGenerator.GenerationContext context; - - public GetIdentityMiddlewareGenerator(ProtocolGenerator.GenerationContext context) { - this.context = context; + public GetIdentityMiddlewareGenerator() { } public static Writable generateAddToProtocolFinalizers() { diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/ResolveAuthSchemeMiddlewareGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/ResolveAuthSchemeMiddlewareGenerator.java index 661df269f..0b76a28c8 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/ResolveAuthSchemeMiddlewareGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/ResolveAuthSchemeMiddlewareGenerator.java @@ -24,17 +24,13 @@ import software.amazon.smithy.go.codegen.MiddlewareIdentifier; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.Writable; -import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.utils.MapUtils; public class ResolveAuthSchemeMiddlewareGenerator { public static final String MIDDLEWARE_NAME = "resolveAuthSchemeMiddleware"; public static final String MIDDLEWARE_ID = "ResolveAuthScheme"; - private final ProtocolGenerator.GenerationContext context; - - public ResolveAuthSchemeMiddlewareGenerator(ProtocolGenerator.GenerationContext context) { - this.context = context; + public ResolveAuthSchemeMiddlewareGenerator() { } public static Writable generateAddToProtocolFinalizers() { diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/SignRequestMiddlewareGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/SignRequestMiddlewareGenerator.java index 8bb1dc5a3..59973eb31 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/SignRequestMiddlewareGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/SignRequestMiddlewareGenerator.java @@ -23,17 +23,13 @@ import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.Writable; import software.amazon.smithy.go.codegen.endpoints.EndpointMiddlewareGenerator; -import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.utils.MapUtils; public class SignRequestMiddlewareGenerator { public static final String MIDDLEWARE_NAME = "signRequestMiddleware"; public static final String MIDDLEWARE_ID = "Signing"; - private final ProtocolGenerator.GenerationContext context; - - public SignRequestMiddlewareGenerator(ProtocolGenerator.GenerationContext context) { - this.context = context; + public SignRequestMiddlewareGenerator() { } public static Writable generateAddToProtocolFinalizers() { diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointMiddlewareGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointMiddlewareGenerator.java index 2712015ce..72a49df38 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointMiddlewareGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointMiddlewareGenerator.java @@ -19,13 +19,13 @@ import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.go.codegen.SmithyGoDependency.SMITHY_TRACING; +import software.amazon.smithy.go.codegen.GoCodegenContext; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.MiddlewareIdentifier; import software.amazon.smithy.go.codegen.Writable; import software.amazon.smithy.go.codegen.auth.GetIdentityMiddlewareGenerator; import software.amazon.smithy.go.codegen.integration.GoIntegration; -import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait; import software.amazon.smithy.utils.MapUtils; @@ -38,10 +38,10 @@ public final class EndpointMiddlewareGenerator { public static final String MIDDLEWARE_NAME = "resolveEndpointV2Middleware"; public static final String MIDDLEWARE_ID = "ResolveEndpointV2"; - private final ProtocolGenerator.GenerationContext context; + private final GoCodegenContext ctx; - public EndpointMiddlewareGenerator(ProtocolGenerator.GenerationContext context) { - this.context = context; + public EndpointMiddlewareGenerator(GoCodegenContext ctx) { + this.ctx = ctx; } public static Writable generateAddToProtocolFinalizers() { @@ -101,8 +101,8 @@ private Writable generateBody() { private Writable generatePreResolutionHooks() { return (GoWriter writer) -> { - for (GoIntegration integration : context.getIntegrations()) { - integration.renderPreEndpointResolutionHook(context.getSettings(), writer, context.getModel()); + for (GoIntegration integration : ctx.integrations()) { + integration.renderPreEndpointResolutionHook(ctx.settings(), writer, ctx.model()); } }; } @@ -186,8 +186,8 @@ private Writable generateMergeAuthProperties() { private Writable generatePostResolutionHooks() { return (GoWriter writer) -> { - for (GoIntegration integration : context.getIntegrations()) { - integration.renderPostEndpointResolutionHook(context.getSettings(), writer, context.getModel()); + for (GoIntegration integration : ctx.integrations()) { + integration.renderPostEndpointResolutionHook(ctx.settings(), writer, ctx.model()); } }; } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParameterBindingsGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParameterBindingsGenerator.java index 054e10e3c..72de2b65c 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParameterBindingsGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParameterBindingsGenerator.java @@ -22,9 +22,10 @@ import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; +import software.amazon.smithy.go.codegen.GoCodegenContext; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.Writable; -import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; +import software.amazon.smithy.rulesengine.language.EndpointRuleSet; import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter; import software.amazon.smithy.rulesengine.traits.ClientContextParamsTrait; import software.amazon.smithy.rulesengine.traits.EndpointBddTrait; @@ -36,14 +37,14 @@ * conditionally through EndpointParameterOperationBindingsGenerator. */ public class EndpointParameterBindingsGenerator { - private final ProtocolGenerator.GenerationContext context; + private final GoCodegenContext ctx; private final Map builtinBindings; - public EndpointParameterBindingsGenerator(ProtocolGenerator.GenerationContext context) { - this.context = context; - this.builtinBindings = context.getIntegrations().stream() - .flatMap(it -> it.getClientPlugins(context.getModel(), context.getService()).stream()) + public EndpointParameterBindingsGenerator(GoCodegenContext ctx) { + this.ctx = ctx; + this.builtinBindings = ctx.integrations().stream() + .flatMap(it -> it.getClientPlugins(ctx.model(), ctx.service()).stream()) .flatMap(it -> it.getEndpointBuiltinBindings().entrySet().stream()) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } @@ -70,8 +71,8 @@ func bindEndpointParams(ctx $context:T, input interface{}, options Options) (*En """, MapUtils.of( "context", GoStdlibTypes.Context.Context, - "builtinBindings", context.getService().hasTrait(EndpointRuleSetTrait.class) - || context.getService().hasTrait(EndpointBddTrait.class) + "builtinBindings", ctx.service().hasTrait(EndpointRuleSetTrait.class) + || ctx.service().hasTrait(EndpointBddTrait.class) ? generateBuiltinBindings() : emptyGoTemplate(), "clientContextBindings", generateClientContextBindings() @@ -80,15 +81,15 @@ func bindEndpointParams(ctx $context:T, input interface{}, options Options) (*En private Writable generateBuiltinBindings() { var bindings = new HashMap(); - for (var integration: context.getIntegrations()) { - var plugins = integration.getClientPlugins(context.getModel(), context.getService()); + for (var integration: ctx.integrations()) { + var plugins = integration.getClientPlugins(ctx.model(), ctx.service()); for (var plugin: plugins) { bindings.putAll(plugin.getEndpointBuiltinBindings()); } } var params = new ArrayList(); - context.getEndpointRules().getParameters().forEach(params::add); + getEndpointRules().getParameters().forEach(params::add); var boundBuiltins = params.stream() .filter(it -> it.isBuiltIn() && bindings.containsKey(it.getBuiltIn().get())) .toList(); @@ -115,13 +116,13 @@ private Writable generateBuiltinBindings() { } private Writable generateClientContextBindings() { - if (!context.getService().hasTrait(ClientContextParamsTrait.class)) { + if (!ctx.service().hasTrait(ClientContextParamsTrait.class)) { return goTemplate(""); } var allParams = new ArrayList(); - context.getEndpointRules().getParameters().forEach(allParams::add); - var contextParams = context.getService().expectTrait(ClientContextParamsTrait.class).getParameters(); + getEndpointRules().getParameters().forEach(allParams::add); + var contextParams = ctx.service().expectTrait(ClientContextParamsTrait.class).getParameters(); var params = allParams.stream() .filter(it -> contextParams.containsKey(it.getName().getName().getValue()) && !it.isBuiltIn()) .toList(); @@ -132,4 +133,16 @@ private Writable generateClientContextBindings() { }); }; } + + private EndpointRuleSet getEndpointRules() { + var service = ctx.service(); + if (service.hasTrait(EndpointRuleSetTrait.class)) { + return EndpointRuleSet.fromNode(service.expectTrait(EndpointRuleSetTrait.class).getRuleSet()); + } + var bddTrait = service.expectTrait(EndpointBddTrait.class); + return EndpointRuleSet.builder() + .version(bddTrait.getVersion().toString()) + .parameters(bddTrait.getParameters()) + .build(); + } } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointResolutionGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointResolutionGenerator.java index d2a008b51..d76de9ac9 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointResolutionGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointResolutionGenerator.java @@ -19,11 +19,11 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; -import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; +import software.amazon.smithy.go.codegen.GoCodegenContext; +import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; -import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.rulesengine.language.EndpointRuleSet; import software.amazon.smithy.rulesengine.traits.EndpointBddTrait; import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait; @@ -62,14 +62,8 @@ public EndpointResolutionGenerator(FnProvider fnProvider) { this.newResolverFn = SymbolUtils.createValueSymbolBuilder(NEW_RESOLVER_FUNC_NAME).build(); } - public void generate(ProtocolGenerator.GenerationContext context) { - if (context.getWriter().isEmpty()) { - throw new CodegenException("writer is required"); - } - - var writer = context.getWriter().get(); - - var serviceShape = context.getService(); + public void generate(GoCodegenContext ctx, GoWriter writer) { + var serviceShape = ctx.service(); var parametersGenerator = EndpointParametersGenerator.builder() .parametersType(parametersType) @@ -85,8 +79,8 @@ public void generate(ProtocolGenerator.GenerationContext context) { .fnProvider(this.fnProvider) .build(); - var bindingGenerator = new EndpointParameterBindingsGenerator(context); - var middlewareGenerator = new EndpointMiddlewareGenerator(context); + var bindingGenerator = new EndpointParameterBindingsGenerator(ctx); + var middlewareGenerator = new EndpointMiddlewareGenerator(ctx); // Ensure rulesfn import for StringSlice used in scope-emitted code. writer.write("var _ = $T(nil)", SymbolUtils.createValueSymbolBuilder( @@ -122,13 +116,8 @@ public void generate(ProtocolGenerator.GenerationContext context) { .write("$W", middlewareGenerator.generate()); } - public void generateTests(ProtocolGenerator.GenerationContext context) { - if (context.getWriter().isEmpty()) { - throw new CodegenException("writer is required"); - } - - var writer = context.getWriter().get(); - var serviceShape = context.getService(); + public void generateTests(GoCodegenContext ctx, GoWriter writer) { + var serviceShape = ctx.service(); Optional ruleset = serviceShape.getTrait(EndpointRuleSetTrait.class) .map((trait) -> EndpointRuleSet.fromNode(trait.getRuleSet())); if (ruleset.isEmpty()) { diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/EventStreamProtocolTestGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/EventStreamProtocolTestGenerator.java index d1a08ea00..345a25154 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/EventStreamProtocolTestGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/EventStreamProtocolTestGenerator.java @@ -61,15 +61,7 @@ public class EventStreamProtocolTestGenerator { // value is treated as an unknown event (UnknownUnionMember) rather // than surfacing an error. "MalformedEventTypeOutput", - "DuplexMalformedEventTypeOutput", - - // FUTURE(serde2): released protocol generation does not handle - // "implicit" payload members (i.e. @eventHeader + no @eventPayload + - // other members), this is fixed in serde2 so just skip for now. - "HeadersAndImplicitPayloadOutput", - "DuplexHeadersAndImplicitPayloadOutput", - "HeadersAndImplicitPayloadInput", - "DuplexHeadersAndImplicitPayloadInput" + "DuplexMalformedEventTypeOutput" ); private final GoSettings settings; diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/GoIntegration.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/GoIntegration.java index 9d39e9fb1..ad0c6d4a1 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/GoIntegration.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/GoIntegration.java @@ -23,6 +23,7 @@ import software.amazon.smithy.go.codegen.GoCodegenContext; import software.amazon.smithy.go.codegen.GoDelegator; import software.amazon.smithy.go.codegen.GoSettings; +import software.amazon.smithy.go.codegen.endpoints.FnProvider; import software.amazon.smithy.go.codegen.GoSettings.ArtifactType; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.TriConsumer; @@ -173,6 +174,14 @@ default List getProtocolGenerators() { return Collections.emptyList(); } + /** + * Gets a custom endpoint rule function provider. Returning null (the default) indicates no custom functions. + * The returned provider is consulted before the built-in default provider. + */ + default FnProvider getEndpointFnProvider() { + return null; + } + /** * Gets a list of server protocol generators to register. Protocol generators should generally be written to accept * the codegen context at construction time, such that all the model information necessary for codegen is available. diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/ProtocolGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/ProtocolGenerator.java index cb6080783..6a81927a7 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/ProtocolGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/ProtocolGenerator.java @@ -29,9 +29,6 @@ import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.Synthetic; -import software.amazon.smithy.go.codegen.auth.AuthGenerator; -import software.amazon.smithy.go.codegen.endpoints.EndpointResolutionGenerator; -import software.amazon.smithy.go.codegen.endpoints.FnGenerator; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.node.ExpectationNotMetException; import software.amazon.smithy.model.shapes.OperationShape; @@ -462,8 +459,6 @@ default void generateEventStreamComponents(GenerationContext context) { * @param context the generation context. */ default void generateEndpointResolution(GenerationContext context) { - var generator = new EndpointResolutionGenerator(new FnGenerator.DefaultFnProvider()); - generator.generate(context); } /** @@ -472,8 +467,6 @@ default void generateEndpointResolution(GenerationContext context) { * @param context the generation context. */ default void generateEndpointResolutionTests(GenerationContext context) { - var generator = new EndpointResolutionGenerator(new FnGenerator.DefaultFnProvider()); - generator.generateTests(context); } /** @@ -482,7 +475,6 @@ default void generateEndpointResolutionTests(GenerationContext context) { * @param context The generation context. */ default void generateAuth(GenerationContext context) { - new AuthGenerator(context).generate(); } /** diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde/SerdeUtil.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde/SerdeUtil.java index 474d77e3a..8430eb058 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde/SerdeUtil.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde/SerdeUtil.java @@ -36,6 +36,9 @@ import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.utils.SmithyInternalApi; +/** + * WARNING 1/29/26: AVOID any new usages of these. + */ @SmithyInternalApi public final class SerdeUtil { private SerdeUtil() {} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/ListDeserializer.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/ListDeserializer.java new file mode 100644 index 000000000..20a4f849a --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/ListDeserializer.java @@ -0,0 +1,196 @@ +package software.amazon.smithy.go.codegen.serde2; + +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import java.util.Map; +import software.amazon.smithy.codegen.core.CodegenException; +import software.amazon.smithy.go.codegen.GoCodegenContext; +import software.amazon.smithy.go.codegen.GoUniverseTypes; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.ProtocolDocumentGenerator; +import software.amazon.smithy.go.codegen.SmithyGoDependency; +import software.amazon.smithy.go.codegen.Writable; +import software.amazon.smithy.go.codegen.util.ShapeUtil; +import software.amazon.smithy.model.shapes.ListShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.traits.SparseTrait; + +public class ListDeserializer implements Writable { + private final GoCodegenContext ctx; + private final ListShape shape; + private final Shape member; + + public ListDeserializer(GoCodegenContext ctx, ListShape shape) { + this.ctx = ctx; + this.shape = shape; + this.member = ShapeUtil.expectMember(ctx.model(), shape); + } + + @Override + public void accept(GoWriter writer) { + writer.addUseImports(SmithyGoDependency.SMITHY); + if (shape.hasTrait(SparseTrait.class)) { + renderSparse(writer); + } else { + renderDense(writer); + } + } + + private void renderDense(GoWriter writer) { + writer.writeGoTemplate(""" + func deserialize$shapeName:L(d smithy.ShapeDeserializer, s *smithy.Schema, v *$symbol:T) error { + var vv $memberSymbol:T + return smithy.ReadList(d, s, func() error { + $zeroValue:W + if err := $deserializeMember:W; err != nil { + return err + } + + *v = append(*v, $cast:W) + return nil + }) + } + """, Map.of( + "shapeName", shape.getId().getName(), + "symbol", ctx.symbolProvider().toSymbol(shape), + "memberSymbol", switch (member.getType()) { + case STRING, ENUM -> ShapeUtil.isEnum(member) + ? GoUniverseTypes.String : ctx.symbolProvider().toSymbol(member); + case INT_ENUM -> GoUniverseTypes.Int32; + case DOCUMENT -> SmithyGoDependency.SMITHY_DOCUMENT.valueSymbol("Value"); + default -> ctx.symbolProvider().toSymbol(member); + }, + "zeroValue", renderZeroValue(), + "deserializeMember", renderDeserializeMember(), + "cast", renderDenseCast() + )); + } + + private void renderSparse(GoWriter writer) { + writer.writeGoTemplate(""" + func deserialize$shapeName:L(d smithy.ShapeDeserializer, s *smithy.Schema, v *$symbol:T) error { + var vv $memberSymbol:T + return smithy.ReadList(d, s, func() error { + if isNil, err := d.ReadNil(s.ListMember()); err != nil { + return err + } else if isNil { + *v = append(*v, nil) + return nil + } + + $zeroValue:W + if err := $deserializeMember:W; err != nil { + return err + } + + *v = append(*v, $cast:W) + return nil + }) + } + """, Map.of( + "shapeName", shape.getId().getName(), + "symbol", ctx.symbolProvider().toSymbol(shape), + "memberSymbol", switch (member.getType()) { + case STRING, ENUM -> ShapeUtil.isEnum(member) + ? GoUniverseTypes.String : ctx.symbolProvider().toSymbol(member); + case INT_ENUM -> GoUniverseTypes.Int32; + case DOCUMENT -> SmithyGoDependency.SMITHY_DOCUMENT.valueSymbol("Value"); + default -> ctx.symbolProvider().toSymbol(member); + }, + "zeroValue", renderZeroValue(), + "deserializeMember", renderDeserializeMember(), + "cast", renderSparseCast() + )); + } + + private Writable renderZeroValue() { + return switch (member.getType()) { + case STRUCTURE -> + goTemplate("vv = $T{}", ctx.symbolProvider().toSymbol(member)); + case LIST, SET, MAP -> + goTemplate("vv = nil"); + default -> goTemplate(""); + }; + } + + private Writable renderSparseCast() { + return switch (member.getType()) { + case STRING, ENUM, INT_ENUM -> ShapeUtil.isEnum(member) + ? goTemplate(""" + func() $T { + ev := $T(vv) + return &ev + }()""", ctx.symbolProvider().toSymbol(member)) + : goTemplate("&vv"); + + // don't need the address-of + case BLOB, LIST, SET, MAP, UNION -> + goTemplate("vv"); + + case DOCUMENT -> renderDocumentCast(); + + default -> goTemplate("&vv"); + }; + } + + private Writable renderDenseCast() { + return switch (member.getType()) { + case STRING, ENUM, INT_ENUM -> ShapeUtil.isEnum(member) + ? goTemplate("$T(vv)", ctx.symbolProvider().toSymbol(member)) + : goTemplate("vv"); + case DOCUMENT -> renderDocumentCast(); + default -> goTemplate("vv"); + }; + } + + private Writable renderDocumentCast() { + var unmarshaler = ProtocolDocumentGenerator.Utilities.getInternalDocumentSymbolBuilder( + ctx.settings(), ProtocolDocumentGenerator.INTERNAL_NEW_DOCUMENT_UNMARSHALER_FUNC).build(); + return goTemplate(""" + func() $T { + if ov, ok := vv.(smithydocument.Opaque); ok { + return $T(ov.Value) + } + return nil + }()""", ctx.symbolProvider().toSymbol(member), unmarshaler); + } + + private Writable renderDeserializeMember() { + return switch (member.getType()) { + case BYTE -> + goTemplate("d.ReadInt8(s.ListMember(), &vv)"); + case SHORT -> + goTemplate("d.ReadInt16(s.ListMember(), &vv)"); + case INTEGER, INT_ENUM -> + goTemplate("d.ReadInt32(s.ListMember(), &vv)"); + case LONG -> + goTemplate("d.ReadInt64(s.ListMember(), &vv)"); + + case FLOAT -> + goTemplate("d.ReadFloat32(s.ListMember(), &vv)"); + case DOUBLE -> + goTemplate("d.ReadFloat64(s.ListMember(), &vv)"); + + case STRING, ENUM -> + goTemplate("d.ReadString(s.ListMember(), &vv)"); + case BOOLEAN -> + goTemplate("d.ReadBool(s.ListMember(), &vv)"); + case TIMESTAMP -> + goTemplate("d.ReadTime(s.ListMember(), &vv)"); + case BLOB -> + goTemplate("d.ReadBlob(s.ListMember(), &vv)"); + + case LIST, SET, MAP, UNION -> + goTemplate("deserialize$L(d, s.ListMember(), &vv)", member.getId().getName()); + case STRUCTURE -> + goTemplate("vv.Deserialize(d)"); + case DOCUMENT -> + goTemplate("d.ReadDocument(s.ListMember(), &vv)"); + + case BIG_INTEGER, BIG_DECIMAL -> + throw new CodegenException("big integer / big decimal unsupported"); + case MEMBER, OPERATION, RESOURCE, SERVICE -> + throw new CodegenException("invalid shape type " + shape.getType()); + }; + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/ListSerializer.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/ListSerializer.java new file mode 100644 index 000000000..5d9a65568 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/ListSerializer.java @@ -0,0 +1,148 @@ +package software.amazon.smithy.go.codegen.serde2; + +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import java.util.Map; +import software.amazon.smithy.codegen.core.CodegenException; +import software.amazon.smithy.go.codegen.GoCodegenContext; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.SmithyGoDependency; +import software.amazon.smithy.go.codegen.Writable; +import software.amazon.smithy.go.codegen.util.ShapeUtil; +import software.amazon.smithy.model.shapes.ListShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.traits.SparseTrait; + +public class ListSerializer implements Writable { + private final GoCodegenContext ctx; + private final ListShape shape; + private final Shape member; + + public ListSerializer(GoCodegenContext ctx, ListShape shape) { + this.ctx = ctx; + this.shape = shape; + this.member = ShapeUtil.expectMember(ctx.model(), shape); + } + + @Override + public void accept(GoWriter writer) { + writer.addUseImports(SmithyGoDependency.SMITHY); + if (member.getType() == software.amazon.smithy.model.shapes.ShapeType.DOCUMENT) { + writer.addUseImports(SmithyGoDependency.SMITHY_DOCUMENT); + } + writer.writeGoTemplate(""" + func serialize$shapeName:L(s smithy.ShapeSerializer, schema *smithy.Schema, v $symbol:T) { + if v == nil { + return + } + s.WriteList(schema) + for _, vv := range v { + $serializeValue:W + } + s.CloseList() + } + """, Map.of( + "shapeName", shape.getId().getName(), + "symbol", ctx.symbolProvider().toSymbol(shape), + "serializeValue", renderSerializeValue() + )); + } + + private Writable renderSerializeValue() { + if (shape.hasTrait(SparseTrait.class)) { + return renderSparseSerializeValue(); + } + return renderDenseSerializeValue(); + } + + private Writable wrapNilCheck(Writable inner) { + return goTemplate(""" + if vv != nil { + $W + } else { + s.WriteNil(schema.ListMember()) + }""", inner); + } + + private Writable renderSparseSerializeValue() { + return switch (member.getType()) { + case BYTE -> + wrapNilCheck(goTemplate("s.WriteInt8(schema.ListMember(), *vv)")); + case SHORT -> + wrapNilCheck(goTemplate("s.WriteInt16(schema.ListMember(), *vv)")); + case INTEGER -> + wrapNilCheck(goTemplate("s.WriteInt32(schema.ListMember(), *vv)")); + case INT_ENUM -> + wrapNilCheck(goTemplate("s.WriteInt32(schema.ListMember(), int32(*vv))")); + case LONG -> + wrapNilCheck(goTemplate("s.WriteInt64(schema.ListMember(), *vv)")); + + case FLOAT -> + wrapNilCheck(goTemplate("s.WriteFloat32(schema.ListMember(), *vv)")); + case DOUBLE -> + wrapNilCheck(goTemplate("s.WriteFloat64(schema.ListMember(), *vv)")); + + case STRING, ENUM -> + ShapeUtil.isEnum(member) + ? wrapNilCheck(goTemplate("s.WriteString(schema.ListMember(), string(*vv))")) + : wrapNilCheck(goTemplate("s.WriteString(schema.ListMember(), *vv)")); + case BOOLEAN -> + wrapNilCheck(goTemplate("s.WriteBool(schema.ListMember(), *vv)")); + case TIMESTAMP -> + wrapNilCheck(goTemplate("s.WriteTime(schema.ListMember(), *vv)")); + case BLOB -> + wrapNilCheck(goTemplate("s.WriteBlob(schema.ListMember(), vv)")); + + case LIST, SET, MAP, UNION -> + wrapNilCheck(goTemplate("serialize$L(s, schema.ListMember(), vv)", member.getId().getName())); + case STRUCTURE -> + wrapNilCheck(goTemplate("s.WriteStruct(schema.ListMember())\nvv.SerializeMembers(s)\ns.CloseStruct()")); + case DOCUMENT -> + wrapNilCheck(goTemplate("s.WriteDocument(schema.ListMember(), &smithydocument.Opaque{Value: vv})")); + + case BIG_INTEGER, BIG_DECIMAL -> + throw new CodegenException("big integer / big decimal unsupported"); + case MEMBER, OPERATION, RESOURCE, SERVICE -> + throw new CodegenException("invalid shape type " + member.getType()); + }; + } + + private Writable renderDenseSerializeValue() { + return switch (member.getType()) { + case BYTE -> + goTemplate("s.WriteInt8(schema.ListMember(), vv)"); + case SHORT -> + goTemplate("s.WriteInt16(schema.ListMember(), vv)"); + case INTEGER, INT_ENUM -> + goTemplate("s.WriteInt32(schema.ListMember(), int32(vv))"); + case LONG -> + goTemplate("s.WriteInt64(schema.ListMember(), vv)"); + + case FLOAT -> + goTemplate("s.WriteFloat32(schema.ListMember(), vv)"); + case DOUBLE -> + goTemplate("s.WriteFloat64(schema.ListMember(), vv)"); + + case STRING, ENUM -> + goTemplate("s.WriteString(schema.ListMember(), string(vv))"); + case BOOLEAN -> + goTemplate("s.WriteBool(schema.ListMember(), vv)"); + case TIMESTAMP -> + goTemplate("s.WriteTime(schema.ListMember(), vv)"); + case BLOB -> + goTemplate("s.WriteBlob(schema.ListMember(), vv)"); + + case LIST, SET, MAP, UNION -> + goTemplate("serialize$L(s, schema.ListMember(), vv)", member.getId().getName()); + case STRUCTURE -> + goTemplate("s.WriteStruct(schema.ListMember())\nvv.SerializeMembers(s)\ns.CloseStruct()"); + case DOCUMENT -> + goTemplate("s.WriteDocument(schema.ListMember(), &smithydocument.Opaque{Value: vv})"); + + case BIG_INTEGER, BIG_DECIMAL -> + throw new CodegenException("big integer / big decimal unsupported"); + case MEMBER, OPERATION, RESOURCE, SERVICE -> + throw new CodegenException("invalid shape type " + member.getType()); + }; + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/MapDeserializer.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/MapDeserializer.java new file mode 100644 index 000000000..5b89bc132 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/MapDeserializer.java @@ -0,0 +1,201 @@ +package software.amazon.smithy.go.codegen.serde2; + +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import java.util.Map; +import software.amazon.smithy.codegen.core.CodegenException; +import software.amazon.smithy.go.codegen.GoCodegenContext; +import software.amazon.smithy.go.codegen.GoUniverseTypes; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.ProtocolDocumentGenerator; +import software.amazon.smithy.go.codegen.SmithyGoDependency; +import software.amazon.smithy.go.codegen.Writable; +import software.amazon.smithy.go.codegen.util.ShapeUtil; +import software.amazon.smithy.model.shapes.MapShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.traits.SparseTrait; + +public class MapDeserializer implements Writable { + private final GoCodegenContext ctx; + private final MapShape shape; + private final Shape value; + + public MapDeserializer(GoCodegenContext ctx, MapShape shape) { + this.ctx = ctx; + this.shape = shape; + this.value = ShapeUtil.expectMember(ctx.model(), shape); + } + + @Override + public void accept(GoWriter writer) { + writer.addUseImports(SmithyGoDependency.SMITHY); + if (shape.hasTrait(SparseTrait.class)) { + renderSparse(writer); + } else { + renderDense(writer); + } + } + + private void renderDense(GoWriter writer) { + writer.writeGoTemplate(""" + func deserialize$shapeName:L(d smithy.ShapeDeserializer, s *smithy.Schema, v *$symbol:T) error { + *v = make($symbol:T) + var vv $valueSymbol:T + return smithy.ReadMap(d, s, func(k string) error { + $zeroValue:W + if err := $deserializeValue:W; err != nil { + return err + } + + (*v)[k] = $cast:W + return nil + }) + } + """, Map.of( + "shapeName", shape.getId().getName(), + "symbol", ctx.symbolProvider().toSymbol(shape), + "valueSymbol", switch (value.getType()) { + case STRING, ENUM -> ShapeUtil.isEnum(value) + ? GoUniverseTypes.String : ctx.symbolProvider().toSymbol(value); + case INT_ENUM -> GoUniverseTypes.Int32; + case DOCUMENT -> SmithyGoDependency.SMITHY_DOCUMENT.valueSymbol("Value"); + default -> ctx.symbolProvider().toSymbol(value); + }, + "zeroValue", renderZeroValue(), + "deserializeValue", renderDeserializeValue(), + "cast", renderDenseCast() + )); + } + + private void renderSparse(GoWriter writer) { + writer.writeGoTemplate(""" + func deserialize$shapeName:L(d smithy.ShapeDeserializer, s *smithy.Schema, v *$symbol:T) error { + *v = make($symbol:T) + var vv $valueSymbol:T + return smithy.ReadMap(d, s, func(k string) error { + if isNil, err := d.ReadNil(s.MapValue()); err != nil { + return err + } else if isNil { + (*v)[k] = nil + return nil + } + + $zeroValue:W + if err := $deserializeValue:W; err != nil { + return err + } + + (*v)[k] = $cast:W + return nil + }) + } + """, Map.of( + "shapeName", shape.getId().getName(), + "symbol", ctx.symbolProvider().toSymbol(shape), + "valueSymbol", switch (value.getType()) { + case STRING, ENUM -> ShapeUtil.isEnum(value) + ? GoUniverseTypes.String : ctx.symbolProvider().toSymbol(value); + case INT_ENUM -> GoUniverseTypes.Int32; + case DOCUMENT -> SmithyGoDependency.SMITHY_DOCUMENT.valueSymbol("Value"); + default -> ctx.symbolProvider().toSymbol(value); + }, + "zeroValue", renderZeroValue(), + "deserializeValue", renderDeserializeValue(), + "cast", renderSparseCast() + )); + } + + private Writable renderZeroValue() { + return switch (value.getType()) { + case STRUCTURE -> + goTemplate("vv = $T{}", ctx.symbolProvider().toSymbol(value)); + case LIST, SET, MAP -> + goTemplate("vv = nil"); + default -> goTemplate(""); + }; + } + + private Writable renderSparseCast() { + return switch (value.getType()) { + case STRING, ENUM, INT_ENUM -> ShapeUtil.isEnum(value) + ? goTemplate(""" + func() $T { + ev := $T(vv) + return &ev + }()""", ctx.symbolProvider().toSymbol(value)) + : goTemplate("&vv"); + + // don't need the address-of + case BLOB, LIST, SET, MAP, UNION -> + goTemplate("vv"); + + case DOCUMENT -> renderDocumentCast(); + + case STRUCTURE -> goTemplate("func() *$T { cp := vv; return &cp }()", + ctx.symbolProvider().toSymbol(value)); + + default -> goTemplate("&vv"); + }; + } + + private Writable renderDenseCast() { + return switch (value.getType()) { + case STRING, ENUM, INT_ENUM -> ShapeUtil.isEnum(value) + ? goTemplate("$T(vv)", ctx.symbolProvider().toSymbol(value)) + : goTemplate("vv"); + case DOCUMENT -> renderDocumentCast(); + default -> goTemplate("vv"); + }; + } + + private Writable renderDocumentCast() { + var unmarshaler = ProtocolDocumentGenerator.Utilities.getInternalDocumentSymbolBuilder( + ctx.settings(), ProtocolDocumentGenerator.INTERNAL_NEW_DOCUMENT_UNMARSHALER_FUNC).build(); + return goTemplate(""" + func() $T { + if ov, ok := vv.(smithydocument.Opaque); ok { + return $T(ov.Value) + } + return nil + }()""", ctx.symbolProvider().toSymbol(value), unmarshaler); + } + + private Writable renderDeserializeValue() { + return switch (value.getType()) { + case BYTE -> + goTemplate("d.ReadInt8(s.MapValue(), &vv)"); + case SHORT -> + goTemplate("d.ReadInt16(s.MapValue(), &vv)"); + case INTEGER, INT_ENUM -> + goTemplate("d.ReadInt32(s.MapValue(), &vv)"); + case LONG -> + goTemplate("d.ReadInt64(s.MapValue(), &vv)"); + + case FLOAT -> + goTemplate("d.ReadFloat32(s.MapValue(), &vv)"); + case DOUBLE -> + goTemplate("d.ReadFloat64(s.MapValue(), &vv)"); + + case STRING, ENUM -> + goTemplate("d.ReadString(s.MapValue(), &vv)"); + case BOOLEAN -> + goTemplate("d.ReadBool(s.MapValue(), &vv)"); + case TIMESTAMP -> + goTemplate("d.ReadTime(s.MapValue(), &vv)"); + case BLOB -> + goTemplate("d.ReadBlob(s.MapValue(), &vv)"); + + case LIST, SET, MAP, UNION -> + goTemplate("deserialize$L(d, s.MapValue(), &vv)", value.getId().getName()); + case STRUCTURE -> + goTemplate("vv.Deserialize(d)"); + case DOCUMENT -> + goTemplate("d.ReadDocument(s.MapValue(), &vv)"); + + case BIG_INTEGER, BIG_DECIMAL -> + throw new CodegenException("big integer / big decimal unsupported"); + case MEMBER, OPERATION, RESOURCE, SERVICE -> + throw new CodegenException("invalid shape type " + value.getType()); + }; + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/MapSerializer.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/MapSerializer.java new file mode 100644 index 000000000..0c01d4238 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/MapSerializer.java @@ -0,0 +1,149 @@ +package software.amazon.smithy.go.codegen.serde2; + +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import java.util.Map; +import software.amazon.smithy.codegen.core.CodegenException; +import software.amazon.smithy.go.codegen.GoCodegenContext; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.SmithyGoDependency; +import software.amazon.smithy.go.codegen.Writable; +import software.amazon.smithy.go.codegen.util.ShapeUtil; +import software.amazon.smithy.model.shapes.MapShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.traits.SparseTrait; + +public class MapSerializer implements Writable { + private final GoCodegenContext ctx; + private final MapShape shape; + private final Shape value; + + public MapSerializer(GoCodegenContext ctx, MapShape shape) { + this.ctx = ctx; + this.shape = shape; + this.value = ShapeUtil.expectMember(ctx.model(), shape); + } + + @Override + public void accept(GoWriter writer) { + writer.addUseImports(SmithyGoDependency.SMITHY); + if (value.getType() == software.amazon.smithy.model.shapes.ShapeType.DOCUMENT) { + writer.addUseImports(SmithyGoDependency.SMITHY_DOCUMENT); + } + writer.writeGoTemplate(""" + func serialize$shapeName:L(s smithy.ShapeSerializer, schema *smithy.Schema, v $symbol:T) { + if v == nil { + return + } + s.WriteMap(schema) + for k, vv := range v { + s.WriteKey(schema.MapKey(), k) + $serializeValue:W + } + s.CloseMap() + } + """, Map.of( + "shapeName", shape.getId().getName(), + "symbol", ctx.symbolProvider().toSymbol(shape), + "serializeValue", renderSerializeValue() + )); + } + + private Writable renderSerializeValue() { + if (shape.hasTrait(SparseTrait.class)) { + return renderSparseSerializeValue(); + } + return renderDenseSerializeValue(); + } + + private Writable wrapNilCheck(Writable inner) { + return goTemplate(""" + if vv != nil { + $W + } else { + s.WriteNil(schema.MapValue()) + }""", inner); + } + + private Writable renderSparseSerializeValue() { + return switch (value.getType()) { + case BYTE -> + wrapNilCheck(goTemplate("s.WriteInt8(schema.MapValue(), *vv)")); + case SHORT -> + wrapNilCheck(goTemplate("s.WriteInt16(schema.MapValue(), *vv)")); + case INTEGER -> + wrapNilCheck(goTemplate("s.WriteInt32(schema.MapValue(), *vv)")); + case INT_ENUM -> + wrapNilCheck(goTemplate("s.WriteInt32(schema.MapValue(), int32(*vv))")); + case LONG -> + wrapNilCheck(goTemplate("s.WriteInt64(schema.MapValue(), *vv)")); + + case FLOAT -> + wrapNilCheck(goTemplate("s.WriteFloat32(schema.MapValue(), *vv)")); + case DOUBLE -> + wrapNilCheck(goTemplate("s.WriteFloat64(schema.MapValue(), *vv)")); + + case STRING, ENUM -> + ShapeUtil.isEnum(value) + ? wrapNilCheck(goTemplate("s.WriteString(schema.MapValue(), string(*vv))")) + : wrapNilCheck(goTemplate("s.WriteString(schema.MapValue(), *vv)")); + case BOOLEAN -> + wrapNilCheck(goTemplate("s.WriteBool(schema.MapValue(), *vv)")); + case TIMESTAMP -> + wrapNilCheck(goTemplate("s.WriteTime(schema.MapValue(), *vv)")); + case BLOB -> + wrapNilCheck(goTemplate("s.WriteBlob(schema.MapValue(), vv)")); + + case LIST, SET, MAP, UNION -> + wrapNilCheck(goTemplate("serialize$L(s, schema.MapValue(), vv)", value.getId().getName())); + case STRUCTURE -> + wrapNilCheck(goTemplate("s.WriteStruct(schema.MapValue())\nvv.SerializeMembers(s)\ns.CloseStruct()")); + case DOCUMENT -> + wrapNilCheck(goTemplate("s.WriteDocument(schema.MapValue(), &smithydocument.Opaque{Value: vv})")); + + case BIG_INTEGER, BIG_DECIMAL -> + throw new CodegenException("big integer / big decimal unsupported"); + case MEMBER, OPERATION, RESOURCE, SERVICE -> + throw new CodegenException("invalid shape type " + value.getType()); + }; + } + + private Writable renderDenseSerializeValue() { + return switch (value.getType()) { + case BYTE -> + goTemplate("s.WriteInt8(schema.MapValue(), vv)"); + case SHORT -> + goTemplate("s.WriteInt16(schema.MapValue(), vv)"); + case INTEGER, INT_ENUM -> + goTemplate("s.WriteInt32(schema.MapValue(), int32(vv))"); + case LONG -> + goTemplate("s.WriteInt64(schema.MapValue(), vv)"); + + case FLOAT -> + goTemplate("s.WriteFloat32(schema.MapValue(), vv)"); + case DOUBLE -> + goTemplate("s.WriteFloat64(schema.MapValue(), vv)"); + + case STRING, ENUM -> + goTemplate("s.WriteString(schema.MapValue(), string(vv))"); + case BOOLEAN -> + goTemplate("s.WriteBool(schema.MapValue(), vv)"); + case TIMESTAMP -> + goTemplate("s.WriteTime(schema.MapValue(), vv)"); + case BLOB -> + goTemplate("s.WriteBlob(schema.MapValue(), vv)"); + + case LIST, SET, MAP, UNION -> + goTemplate("serialize$L(s, schema.MapValue(), vv)", value.getId().getName()); + case STRUCTURE -> + goTemplate("s.WriteStruct(schema.MapValue())\nvv.SerializeMembers(s)\ns.CloseStruct()"); + case DOCUMENT -> + goTemplate("s.WriteDocument(schema.MapValue(), &smithydocument.Opaque{Value: vv})"); + + case BIG_INTEGER, BIG_DECIMAL -> + throw new CodegenException("big integer / big decimal unsupported"); + case MEMBER, OPERATION, RESOURCE, SERVICE -> + throw new CodegenException("invalid shape type " + value.getType()); + }; + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/Serde2DeserializeResponseMiddleware.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/Serde2DeserializeResponseMiddleware.java new file mode 100644 index 000000000..7aefb9615 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/Serde2DeserializeResponseMiddleware.java @@ -0,0 +1,59 @@ +package software.amazon.smithy.go.codegen.serde2; + +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; +import static software.amazon.smithy.go.codegen.SymbolUtils.buildPackageSymbol; +import static software.amazon.smithy.go.codegen.SymbolUtils.pointerTo; + +import java.util.LinkedHashMap; +import java.util.Map; +import software.amazon.smithy.codegen.core.Symbol; +import software.amazon.smithy.go.codegen.SmithyGoDependency; +import software.amazon.smithy.go.codegen.Writable; +import software.amazon.smithy.go.codegen.middleware.DeserializeStepMiddleware; + +public class Serde2DeserializeResponseMiddleware extends DeserializeStepMiddleware { + @Override + public String getStructName() { + return "deserializeResponseMiddleware"; + } + + @Override + public String getId() { + return "OperationDeserializer"; + } + + @Override + public Map getFields() { + var fields = new LinkedHashMap(); + fields.put("options", pointerTo(buildPackageSymbol("Options"))); + fields.put("operationSchema", SmithyGoDependency.SMITHY.pointableSymbol("OperationSchema")); + fields.put("output", SmithyGoDependency.SMITHY.interfaceSymbol("Deserializable")); + return fields; + } + + @Override + public Writable getFuncBody() { + return goTemplate(""" + out, md, err := next.HandleDeserialize(ctx, in) + if err != nil { + return out, md, err + } + + resp, ok := out.RawResponse.(*smithyhttp.Response) + if !ok { + return out, md, &smithy.DeserializationError{Err: fmt.Errorf("unknown transport type %T", out.RawResponse)} + } + + _, span := tracing.StartSpan(ctx, "OperationDeserializer") + endTimer := startMetricTimer(ctx, "client.call.deserialization_duration") + + err = m.options.Protocol.DeserializeResponse(ctx, m.operationSchema, TypeRegistry, resp, m.output) + out.Result = m.output + + endTimer() + span.End() + + return out, md, err + """); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/Serde2EventStreamMiddleware.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/Serde2EventStreamMiddleware.java new file mode 100644 index 000000000..8b7a25e43 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/Serde2EventStreamMiddleware.java @@ -0,0 +1,361 @@ +package software.amazon.smithy.go.codegen.serde2; + +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; +import static software.amazon.smithy.go.codegen.SymbolUtils.buildPackageSymbol; +import static software.amazon.smithy.go.codegen.SymbolUtils.pointerTo; + +import java.util.Map; +import software.amazon.smithy.codegen.core.Symbol; +import software.amazon.smithy.go.codegen.EventStreamGenerator; +import software.amazon.smithy.go.codegen.GoCodegenContext; +import software.amazon.smithy.go.codegen.SchemaGenerator; +import software.amazon.smithy.go.codegen.SmithyGoDependency; +import software.amazon.smithy.go.codegen.Writable; +import software.amazon.smithy.go.codegen.middleware.DeserializeStepMiddleware; +import software.amazon.smithy.model.knowledge.EventStreamIndex; +import software.amazon.smithy.model.shapes.OperationShape; +import software.amazon.smithy.model.shapes.UnionShape; +import software.amazon.smithy.utils.MapUtils; + +public class Serde2EventStreamMiddleware extends DeserializeStepMiddleware { + private final GoCodegenContext ctx; + private final OperationShape operation; + + public Serde2EventStreamMiddleware(GoCodegenContext ctx, OperationShape operation) { + this.ctx = ctx; + this.operation = operation; + } + + @Override + public String getStructName() { + return String.format("deserializeOpEventStream%s", + operation.getId().getName(ctx.service())); + } + + @Override + public String getId() { + return "OperationEventStreamDeserializer"; + } + + @Override + public Map getFields() { + return Map.of( + "options", pointerTo(buildPackageSymbol("Options")) + ); + } + + @Override + public Writable getFuncBody() { + var model = ctx.model(); + var service = ctx.service(); + var symbolProvider = ctx.symbolProvider(); + var streamIndex = EventStreamIndex.of(model); + + var inputInfo = streamIndex.getInputInfo(operation); + var outputInfo = streamIndex.getOutputInfo(operation); + + var outputShape = model.expectShape(operation.getOutputShape()); + var outputSymbol = symbolProvider.toSymbol(outputShape); + + var opEventStreamConstructor = EventStreamGenerator + .getEventStreamOperationStructureConstructor(service, operation); + var opEventStreamSymbol = EventStreamGenerator + .getEventStreamOperationStructureSymbol(service, operation); + + var isV2 = EventStreamGenerator.isV2EventStream(model, operation); + + if (isV2) { + return v2FuncBody(inputInfo, outputInfo, outputSymbol, opEventStreamConstructor, opEventStreamSymbol); + } + + return goTemplate(""" + out, md, err := next.HandleDeserialize(ctx, in) + if err != nil { + return out, md, err + } + + $respDecl:L, ok := out.RawResponse.($smithyhttpResponse:P) + if !ok { + return out, md, $fmtErrorf:T("unknown transport type: %T", out.RawResponse) + } + + output, ok := out.Result.($output:P) + if out.Result != nil && !ok { + return out, md, $fmtErrorf:T("unexpected output result type %T, expected $output:P", out.Result) + } else if out.Result == nil { + output = &$output:T{} + out.Result = output + } + + $writerSetup:W + $initialRequest:W + $readerSetup:W + $initialResponse:W + + output.eventStream = $esConstructor:T(func(stream $esStruct:P) { + $wireWriter:W + $wireReader:W + }) + + go output.eventStream.waitStreamClose() + + return out, md, nil + """, + Map.ofEntries( + Map.entry("smithyhttpResponse", + SmithyGoDependency.SMITHY_HTTP_TRANSPORT.pointableSymbol("Response")), + Map.entry("fmtErrorf", SmithyGoDependency.FMT.func("Errorf")), + Map.entry("output", outputSymbol), + Map.entry("respDecl", outputInfo.isPresent() ? "resp" : "_"), + Map.entry("esConstructor", opEventStreamConstructor), + Map.entry("esStruct", opEventStreamSymbol), + Map.entry("writerSetup", inputInfo.isPresent() + ? writerSetup(inputInfo.get().getEventStreamTarget().asUnionShape().get()) + : (Writable) w -> {}), + Map.entry("initialRequest", inputInfo.isPresent() + ? initialRequest() + : (Writable) w -> {}), + Map.entry("readerSetup", outputInfo.isPresent() + ? readerSetup(outputInfo.get().getEventStreamTarget().asUnionShape().get()) + : (Writable) w -> {}), + Map.entry("initialResponse", outputInfo.isPresent() + ? initialResponse(outputShape) + : (Writable) w -> {}), + Map.entry("wireWriter", inputInfo.isPresent() + ? (Writable) w -> w.write("stream.Writer = eventWriter") + : (Writable) w -> {}), + Map.entry("wireReader", outputInfo.isPresent() + ? (Writable) w -> w.write("stream.Reader = eventReader") + : (Writable) w -> {}) + )); + } + + private Writable writerSetup(UnionShape inputEventStream) { + var service = ctx.service(); + var schemaName = SchemaGenerator.getSchemaRef(inputEventStream, service); + var adapterName = EventStreamGenerator.getEventStreamWriterAdapterName(service, inputEventStream); + + return goTemplate(""" + inputStreamWriter := $getInputStreamWriter:T(ctx) + if inputStreamWriter == nil { + return out, md, $fmtErrorf:T("input stream writer not found in context") + } + if rscheme := getResolvedAuthScheme(ctx); rscheme != nil { + if es, ok := rscheme.Scheme.Signer().($eventStreamSigner:T); ok { + req, _ := in.Request.($smithyhttpRequest:P) + msgSigner, serr := es.NewMessageSigner(ctx, req, getIdentity(ctx), rscheme.SignerProperties) + if serr != nil { + return out, md, $fmtErrorf:T("event stream signer: %w", serr) + } + inputStreamWriter = $newSigningWriter:T(inputStreamWriter, msgSigner) + } + } + eventWriter := &$adapter:L{ + writer: $newWriter:T(m.options.Protocol, $schema:L, inputStreamWriter), + } + defer func() { + if err != nil { + _ = eventWriter.Close() + } + }() + """, + MapUtils.of( + "getInputStreamWriter", + SmithyGoDependency.SMITHY_HTTP_TRANSPORT.func("GetInputStreamWriter"), + "fmtErrorf", SmithyGoDependency.FMT.func("Errorf"), + "adapter", adapterName, + "newWriter", + SmithyGoDependency.SMITHY_HTTP_TRANSPORT.func("NewEventStreamWriter"), + "eventStreamSigner", + SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("EventStreamSigner"), + "smithyhttpRequest", + SmithyGoDependency.SMITHY_HTTP_TRANSPORT.pointableSymbol("Request"), + "newSigningWriter", + SmithyGoDependency.SMITHY_EVENTSTREAM.func("NewSigningWriter"), + "schema", schemaName + )); + } + + private Writable readerSetup(UnionShape outputEventStream) { + var service = ctx.service(); + var schemaName = SchemaGenerator.getSchemaRef(outputEventStream, service); + var adapterConstructor = EventStreamGenerator + .getEventStreamReaderAdapterConstructor(service, outputEventStream); + + return goTemplate(""" + eventReader := $newAdapter:L( + $newReader:T(m.options.Protocol, $schema:L, TypeRegistry, resp.Body), + ) + defer func() { + if err != nil { + _ = eventReader.Close() + } + }() + """, + MapUtils.of( + "newAdapter", adapterConstructor, + "newReader", + SmithyGoDependency.SMITHY_HTTP_TRANSPORT.func("NewEventStreamReader"), + "schema", schemaName + )); + } + + private Writable initialRequest() { + var inputShape = ctx.model().expectShape(operation.getInputShape()); + var inputSchemaName = SchemaGenerator.getSchemaRef(inputShape, ctx.service()); + + return goTemplate(""" + if m.options.Protocol.HasInitialEventMessage() { + opInput, ok := getOperationInput(ctx).($serializable:T) + if !ok { + return out, md, $fmtErrorf:T("operation input is not serializable") + } + if err = m.options.Protocol.SerializeInitialRequest($inputSchema:L, opInput, inputStreamWriter); err != nil { + return out, md, $fmtErrorf:T("serialize initial request: %w", err) + } + } + """, + MapUtils.of( + "serializable", SmithyGoDependency.SMITHY.valueSymbol("Serializable"), + "fmtErrorf", SmithyGoDependency.FMT.func("Errorf"), + "inputSchema", inputSchemaName + )); + } + + private Writable initialResponse(software.amazon.smithy.model.shapes.Shape outputShape) { + var outputSchemaName = SchemaGenerator.getSchemaRef(outputShape, ctx.service()); + + return goTemplate(""" + if m.options.Protocol.HasInitialEventMessage() { + if err = m.options.Protocol.DeserializeInitialResponse($outputSchema:L, resp.Body, output); err != nil { + return out, md, $fmtErrorf:T("deserialize initial response: %w", err) + } + } + """, + MapUtils.of( + "fmtErrorf", SmithyGoDependency.FMT.func("Errorf"), + "outputSchema", outputSchemaName + )); + } + + private Writable v2FuncBody( + java.util.Optional inputInfo, + java.util.Optional outputInfo, + Symbol outputSymbol, + Symbol opEventStreamConstructor, + Symbol opEventStreamSymbol) { + + // For v2 (early-return) event streams, we must: + // 1. Set up the writer (from the pipe in context) + // 2. Set up the reader using an async pipe (not the response body directly) + // 3. Wire up the event stream and send PartialResult to unblock the caller + // 4. THEN call next.HandleDeserialize (sends the HTTP request) + // 5. Pipe the response body into the async reader + // 6. Add output to metadata for the Build-step middleware + return goTemplate(""" + var out $deserializeOutput:T + var md $metadata:T + var err error + + output := &$output:T{} + output.initialReply = make(chan $initialReply:T, 1) + + $writerSetup:W + $readerSetup:W + + output.eventStream = $esConstructor:T(func(stream $esStruct:P) { + $wireWriter:W + $wireReader:W + }) + + go output.eventStream.waitStreamClose() + + prc, _ := ctx.Value(partialResultChan{}).(chan PartialResult) + if prc != nil { + select { + case <-prc: + default: + } + prc <- PartialResult{ + Output: output, + Metadata: $newMetadata:T{}, + } + } + + out, md, err = next.HandleDeserialize(ctx, in) + if err != nil { + $asyncError:W + return out, md, err + } + + resp, ok := out.RawResponse.($smithyhttpResponse:P) + if !ok { + return out, md, $fmtErrorf:T("unknown transport type: %T", out.RawResponse) + } + + $asyncPipe:W + $addToMetadata:T(&md, output) + return out, md, nil + """, + Map.ofEntries( + Map.entry("output", outputSymbol), + Map.entry("deserializeOutput", + SmithyGoDependency.SMITHY_MIDDLEWARE.valueSymbol("DeserializeOutput")), + Map.entry("metadata", + SmithyGoDependency.SMITHY_MIDDLEWARE.valueSymbol("Metadata")), + Map.entry("initialReply", + EventStreamGenerator.getEventStreamInitialReplyStructureSymbol( + ctx.service(), operation)), + Map.entry("smithyhttpResponse", + SmithyGoDependency.SMITHY_HTTP_TRANSPORT.pointableSymbol("Response")), + Map.entry("fmtErrorf", SmithyGoDependency.FMT.func("Errorf")), + Map.entry("esConstructor", opEventStreamConstructor), + Map.entry("esStruct", opEventStreamSymbol), + Map.entry("newMetadata", + SmithyGoDependency.SMITHY_MIDDLEWARE.valueSymbol("Metadata")), + Map.entry("addToMetadata", + SmithyGoDependency.SMITHY_MIDDLEWARE.func("AddEventStreamOutputToMetadata")), + Map.entry("writerSetup", inputInfo.isPresent() + ? writerSetup(inputInfo.get().getEventStreamTarget().asUnionShape().get()) + : (Writable) w -> {}), + Map.entry("wireWriter", inputInfo.isPresent() + ? (Writable) w -> w.write("stream.Writer = eventWriter") + : (Writable) w -> {}), + Map.entry("readerSetup", outputInfo.isPresent() + ? v2ReaderSetup(outputInfo.get().getEventStreamTarget().asUnionShape().get()) + : (Writable) w -> {}), + Map.entry("wireReader", outputInfo.isPresent() + ? (Writable) w -> w.write("stream.Reader = eventReader") + : (Writable) w -> {}), + Map.entry("asyncError", outputInfo.isPresent() + ? (Writable) w -> w.write(""" + asyncResult <- deserializeResult{err: err} + middleware.AddEventStreamOutputToMetadata(&md, output)""") + : (Writable) w -> {}), + Map.entry("asyncPipe", outputInfo.isPresent() + ? (Writable) w -> w.write("asyncResult <- deserializeResult{reader: resp.Body}") + : (Writable) w -> {}) + )); + } + + private Writable v2ReaderSetup(UnionShape outputEventStream) { + var service = ctx.service(); + var schemaName = SchemaGenerator.getSchemaRef(outputEventStream, service); + var adapterConstructor = EventStreamGenerator + .getEventStreamReaderAdapterConstructor(service, outputEventStream); + + return goTemplate(""" + asyncResult := make(chan deserializeResult, 1) + asyncReader := newAsyncEventStreamReader(asyncResult) + eventReader := $newAdapter:L( + $newReader:T(m.options.Protocol, $schema:L, TypeRegistry, asyncReader.pipeReader), + ) + """, + MapUtils.of( + "newAdapter", adapterConstructor, + "newReader", + SmithyGoDependency.SMITHY_HTTP_TRANSPORT.func("NewEventStreamReader"), + "schema", schemaName + )); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/Serde2SerializeRequestMiddleware.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/Serde2SerializeRequestMiddleware.java new file mode 100644 index 000000000..8e7e39187 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/Serde2SerializeRequestMiddleware.java @@ -0,0 +1,63 @@ +package software.amazon.smithy.go.codegen.serde2; + +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; +import static software.amazon.smithy.go.codegen.SymbolUtils.buildPackageSymbol; +import static software.amazon.smithy.go.codegen.SymbolUtils.pointerTo; + +import java.util.LinkedHashMap; +import java.util.Map; +import software.amazon.smithy.codegen.core.Symbol; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.SmithyGoDependency; +import software.amazon.smithy.go.codegen.Writable; +import software.amazon.smithy.go.codegen.middleware.SerializeStepMiddleware; + +public class Serde2SerializeRequestMiddleware extends SerializeStepMiddleware { + @Override + public String getStructName() { + return "serializeRequestMiddleware"; + } + + @Override + public String getId() { + return "OperationSerializer"; + } + + @Override + public Map getFields() { + var fields = new LinkedHashMap(); + fields.put("options", pointerTo(buildPackageSymbol("Options"))); + fields.put("operationSchema", SmithyGoDependency.SMITHY.pointableSymbol("OperationSchema")); + return fields; + } + + @Override + public Writable getFuncBody() { + return GoWriter.goTemplate(""" + $D + req, ok := in.Request.(*smithyhttp.Request) + if !ok { + return middleware.SerializeOutput{}, middleware.Metadata{}, fmt.Errorf("unexpected transport type %T", in.Request) + } + + input, ok := in.Parameters.(smithy.Serializable) + if !ok { + return middleware.SerializeOutput{}, middleware.Metadata{}, fmt.Errorf("input %T is not Serializable", in.Request) + } + + _, span := tracing.StartSpan(ctx, "OperationSerializer") + endTimer := startMetricTimer(ctx, "client.call.serialization_duration") + + err := m.options.Protocol.SerializeRequest(ctx, m.operationSchema, input, req) + + endTimer() + span.End() + + if err != nil { + return middleware.SerializeOutput{}, middleware.Metadata{}, err + } + + return next.HandleSerialize(ctx, in) + """, SmithyGoDependency.SMITHY_TRACING); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/StructureDeserializer.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/StructureDeserializer.java new file mode 100644 index 000000000..766685cb4 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/StructureDeserializer.java @@ -0,0 +1,146 @@ +package software.amazon.smithy.go.codegen.serde2; + +import java.util.Comparator; +import software.amazon.smithy.go.codegen.GoCodegenContext; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.ProtocolDocumentGenerator; +import software.amazon.smithy.go.codegen.SchemaGenerator; +import software.amazon.smithy.go.codegen.SmithyGoDependency; +import software.amazon.smithy.go.codegen.UnsupportedShapeException; +import software.amazon.smithy.go.codegen.Writable; +import software.amazon.smithy.go.codegen.knowledge.GoPointableIndex; +import software.amazon.smithy.go.codegen.util.ShapeUtil; +import software.amazon.smithy.model.loader.Prelude; +import software.amazon.smithy.model.shapes.MemberShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.StructureShape; +import software.amazon.smithy.model.traits.StreamingTrait; + +public class StructureDeserializer implements Writable { + private final GoCodegenContext ctx; + private final StructureShape shape; + private final GoPointableIndex nilIndex; + + public StructureDeserializer(GoCodegenContext ctx, StructureShape shape) { + this.ctx = ctx; + this.shape = shape; + + this.nilIndex = GoPointableIndex.of(ctx.model()); + } + + @Override + public void accept(GoWriter writer) { + writer.addImport(ctx.settings().getModuleName() + "/schemas", "schemas"); + if (Prelude.isPublicPreludeShape(shape)) { + writer.addUseImports(SmithyGoDependency.SMITHY_PRELUDE); + } + writer.addUseImports(SmithyGoDependency.SMITHY); + + var symbol = ctx.symbolProvider().toSymbol(shape); + var members = shape.members().stream() + .filter(m -> !StreamingTrait.isEventStream(ctx.model(), m)) + .filter(m -> !ctx.model().expectShape(m.getTarget()).hasTrait(StreamingTrait.class)) + .sorted(Comparator.comparing(MemberShape::getMemberName)) + .toList(); + writer.openBlock("func (v *$L) Deserialize(d smithy.ShapeDeserializer) error {", "}", symbol.getName(), () -> { + writer.openBlock("return smithy.ReadStruct(d, $L, func(s *smithy.Schema) error {", "})", SchemaGenerator.getSchemaRef(shape, ctx.service()), () -> { + writer.openBlock("switch s {", "}", () -> { + for (var member : members) { + writer.write("case $L:", SchemaGenerator.getMemberSchemaRef(shape, member, ctx.service())); + renderMember(writer, member, ctx.model().expectShape(member.getTarget()), "v." + ctx.symbolProvider().toMemberName(member)); + } + }); + writer.write("return nil"); + }); + }); + } + + private void renderMember(GoWriter writer, MemberShape member, Shape target, String ident) { + var schemaName = SchemaGenerator.getMemberSchemaRef(shape, member, ctx.service()); + var isNillable = nilIndex.isNillable(member); + switch (target.getType()) { + case BYTE -> + writeReadScalar(writer, isNillable, ident, schemaName, "ReadInt8", "int8"); + case SHORT -> + writeReadScalar(writer, isNillable, ident, schemaName, "ReadInt16", "int16"); + case INTEGER -> + writeReadScalar(writer, isNillable, ident, schemaName, "ReadInt32", "int32"); + case LONG -> + writeReadScalar(writer, isNillable, ident, schemaName, "ReadInt64", "int64"); + + case FLOAT -> + writeReadScalar(writer, isNillable, ident, schemaName, "ReadFloat32", "float32"); + case DOUBLE -> + writeReadScalar(writer, isNillable, ident, schemaName, "ReadFloat64", "float64"); + + case STRING, ENUM -> { + if (ShapeUtil.isEnum(target)) { + writer.write(""" + var ev string + if err := d.ReadString($1L, &ev); err != nil { + return err + } + $2L = $3T(ev) + return nil""", schemaName, ident, ctx.symbolProvider().toSymbol(target)); + } else { + writeReadScalar(writer, isNillable, ident, schemaName, "ReadString", "string"); + } + } + case INT_ENUM -> + writer.write(""" + var ev int32 + if err := d.ReadInt32($1L, &ev); err != nil { + return err + } + $2L = $3T(ev) + return nil""", schemaName, ident, ctx.symbolProvider().toSymbol(target)); + case BOOLEAN -> + writeReadScalar(writer, isNillable, ident, schemaName, "ReadBool", "bool"); + case TIMESTAMP -> + writeReadScalar(writer, isNillable, ident, schemaName, "ReadTime", "time.Time"); + case BLOB -> + writer.write("return d.ReadBlob($L, &$L)", schemaName, ident); + + case LIST, SET, MAP, UNION -> + writer.write("return deserialize$L(d, $L, &$L)", target.getId().getName(), schemaName, ident); + case STRUCTURE -> { + if (nilIndex.isNillable(member)) { + writer.write(""" + $1L = &$2T{} + return $1L.Deserialize(d)""", ident, ctx.symbolProvider().toSymbol(target)); + } else { + writer.write("return $L.Deserialize(d)", ident); + } + } + case DOCUMENT -> { + var unmarshaler = ProtocolDocumentGenerator.Utilities.getInternalDocumentSymbolBuilder( + ctx.settings(), ProtocolDocumentGenerator.INTERNAL_NEW_DOCUMENT_UNMARSHALER_FUNC).build(); + writer.addUseImports(SmithyGoDependency.SMITHY_DOCUMENT); + writer.write(""" + var dv smithydocument.Value + if err := d.ReadDocument($L, &dv); err != nil { + return err + } + if ov, ok := dv.(smithydocument.Opaque); ok { + $L = $T(ov.Value) + } + return nil""", schemaName, ident, unmarshaler); + } + + // FUTURE(602) + case BIG_INTEGER, BIG_DECIMAL -> throw new UnsupportedShapeException(target.getType()); + + // invalid in this context + case MEMBER, SERVICE, RESOURCE, OPERATION -> throw new UnsupportedShapeException(target.getType()); + } + } + + private void writeReadScalar(GoWriter writer, boolean isNillable, String ident, String schemaName, + String readMethod, String goType) { + if (isNillable) { + writer.write("$1L = new($4L)\nreturn d.$3L($2L, $1L)", ident, schemaName, readMethod, goType); + } else { + writer.write("return d.$3L($2L, &$1L)", ident, schemaName, readMethod); + } + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/StructureSerializer.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/StructureSerializer.java new file mode 100644 index 000000000..629fc7f86 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/StructureSerializer.java @@ -0,0 +1,130 @@ +package software.amazon.smithy.go.codegen.serde2; + +import java.util.Comparator; +import software.amazon.smithy.codegen.core.CodegenException; +import software.amazon.smithy.go.codegen.GoCodegenContext; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.SchemaGenerator; +import software.amazon.smithy.go.codegen.SmithyGoDependency; +import software.amazon.smithy.go.codegen.UnsupportedShapeException; +import software.amazon.smithy.go.codegen.Writable; +import software.amazon.smithy.go.codegen.knowledge.GoPointableIndex; +import software.amazon.smithy.go.codegen.util.ShapeUtil; +import software.amazon.smithy.model.loader.Prelude; +import software.amazon.smithy.model.shapes.MemberShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.StructureShape; +import software.amazon.smithy.model.traits.StreamingTrait; +import software.amazon.smithy.utils.SmithyInternalApi; + +@SmithyInternalApi +public final class StructureSerializer implements Writable { + private final GoCodegenContext ctx; + private final StructureShape shape; + private final GoPointableIndex nilIndex; + + public StructureSerializer(GoCodegenContext ctx, StructureShape shape) { + this.ctx = ctx; + this.shape = shape; + + this.nilIndex = GoPointableIndex.of(ctx.model()); + } + + @Override + public void accept(GoWriter writer) { + writer.addImport(ctx.settings().getModuleName() + "/schemas", "schemas"); + if (Prelude.isPublicPreludeShape(shape)) { + writer.addUseImports(SmithyGoDependency.SMITHY_PRELUDE); + } + + var symbol = ctx.symbolProvider().toSymbol(shape); + var members = shape.members().stream() + .filter(m -> !StreamingTrait.isEventStream(ctx.model(), m)) + .filter(m -> !ctx.model().expectShape(m.getTarget()).hasTrait(StreamingTrait.class)) + .sorted(Comparator.comparing(MemberShape::getMemberName)) + .toList(); + writer.addUseImports(SmithyGoDependency.SMITHY); + var schemaRef = SchemaGenerator.getSchemaRef(shape, ctx.service()); + writer.openBlock("func (v *$L) Serialize(s smithy.ShapeSerializer) {", "}", symbol.getName(), () -> { + writer.write("s.WriteStruct($L)", schemaRef); + writer.write("v.SerializeMembers(s)"); + writer.write("s.CloseStruct()"); + }); + writer.write(""); + writer.openBlock("func (v *$L) SerializeMembers(s smithy.ShapeSerializer) {", "}", symbol.getName(), () -> { + for (var member : members) { + var target = ShapeUtil.expectMember(ctx.model(), shape, member.getMemberName()); + var ident = String.format("v.%s", ctx.symbolProvider().toMemberName(member)); + generateSerializeMember(writer, member, target, ident); + } + }); + writer.write(""); + } + + private void generateSerializeMember(GoWriter writer, MemberShape member, Shape target, String ident) { + var schemaName = SchemaGenerator.getMemberSchemaRef(shape, member, ctx.service()); + var isNillable = nilIndex.isNillable(member); + switch (target.getType()) { + case BYTE -> + writeScalar(writer, isNillable, ident, "0", "WriteInt8", schemaName); + case SHORT -> + writeScalar(writer, isNillable, ident, "0", "WriteInt16", schemaName); + case INTEGER -> + writeScalar(writer, isNillable, ident, "0", "WriteInt32", schemaName); + case LONG -> + writeScalar(writer, isNillable, ident, "0", "WriteInt64", schemaName); + + case FLOAT -> + writeScalar(writer, isNillable, ident, "0", "WriteFloat32", schemaName); + case DOUBLE -> + writeScalar(writer, isNillable, ident, "0", "WriteFloat64", schemaName); + + case STRING, ENUM -> { + if (ShapeUtil.isEnum(target)) { + writer.write("if $1L != \"\" { s.WriteString($2L, string($1L)) }", ident, schemaName); + } else { + writeScalar(writer, isNillable, ident, "\"\"", "WriteString", schemaName); + } + } + case INT_ENUM -> + writer.write("if $1L != 0 { s.WriteInt32($2L, int32($1L)) }", ident, schemaName); + case BOOLEAN -> + writeScalar(writer, isNillable, ident, "false", "WriteBool", schemaName); + case TIMESTAMP -> + writeScalar(writer, isNillable, ident, "", "WriteTime", schemaName); + case BLOB -> + writer.write("if $2L != nil { s.WriteBlob($1L, $2L) }", schemaName, ident); + + case LIST, SET, MAP, UNION -> + writer.write("serialize$L(s, $L, $L)", target.getId().getName(), schemaName, ident); + case STRUCTURE -> + writer.write("if $2L != nil { s.WriteStruct($1L)\n$2L.SerializeMembers(s)\ns.CloseStruct() }", schemaName, ident); + case DOCUMENT -> { + writer.addUseImports(SmithyGoDependency.SMITHY_DOCUMENT); + writer.write("s.WriteDocument($L, &smithydocument.Opaque{Value: $L})", schemaName, ident); + } + + // FUTURE(602) + case BIG_INTEGER, BIG_DECIMAL -> throw new UnsupportedShapeException(target.getType()); + + // invalid in this context + case MEMBER, SERVICE, RESOURCE, OPERATION -> throw new CodegenException("invalid shape " + target.getType()); + } + } + + // Generates a nil-guarded (pointer) or zero-guarded (value) scalar write. + // writeMethod is e.g. "WriteInt32". schemaName is the schema ref. ident is + // the Go expression for the member value (e.g. "v.Foo"). + private void writeScalar(GoWriter writer, boolean isNillable, String ident, String zeroValue, + String writeMethod, String schemaName) { + if (isNillable) { + writer.write("if $1L != nil { s.$3L($2L, *$1L) }", ident, schemaName, writeMethod); + } else { + if (zeroValue.isEmpty()) { + writer.write("if !$1L.IsZero() { s.$3L($2L, $1L) }", ident, schemaName, writeMethod); + } else { + writer.write("if $1L != " + zeroValue + " { s.$3L($2L, $1L) }", ident, schemaName, writeMethod); + } + } + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/UnionDeserializer.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/UnionDeserializer.java new file mode 100644 index 000000000..571067e3e --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/UnionDeserializer.java @@ -0,0 +1,70 @@ +package software.amazon.smithy.go.codegen.serde2; + +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import java.util.Comparator; +import java.util.Map; +import software.amazon.smithy.go.codegen.GoCodegenContext; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.SchemaGenerator; +import software.amazon.smithy.go.codegen.SmithyGoDependency; +import software.amazon.smithy.go.codegen.Writable; +import software.amazon.smithy.model.shapes.MemberShape; +import software.amazon.smithy.model.shapes.UnionShape; + +public class UnionDeserializer implements Writable { + private final GoCodegenContext ctx; + private final UnionShape shape; + + public UnionDeserializer(GoCodegenContext ctx, UnionShape shape) { + this.ctx = ctx; + this.shape = shape; + } + + @Override + public void accept(GoWriter writer) { + writer.addUseImports(SmithyGoDependency.SMITHY); + writer.addImport(ctx.settings().getModuleName() + "/schemas", "schemas"); + + var symbol = ctx.symbolProvider().toSymbol(shape); + var members = shape.members().stream() + .sorted(Comparator.comparing(MemberShape::getMemberName)) + .toList(); + + writer.writeGoTemplate(""" + func deserialize$shapeName:L(d smithy.ShapeDeserializer, s *smithy.Schema, v *$symbol:T) error { + return smithy.ReadUnion(d, s, func(ms *smithy.Schema) error { + switch ms { + $cases:W + } + return nil + }) + } + """, Map.of( + "shapeName", shape.getId().getName(), + "symbol", symbol, + "cases", renderCases(members) + )); + } + + private Writable renderCases(java.util.List members) { + return (GoWriter w) -> { + var unionSymbol = ctx.symbolProvider().toSymbol(shape); + for (var member : members) { + var memberName = ctx.symbolProvider().toMemberName(member); + var variantSchema = SchemaGenerator.getMemberSchemaRef(shape, member, ctx.service()); + + var memberSymbol = unionSymbol.toBuilder() + .name(memberName) + .build(); + + w.write("case $L:", variantSchema); + w.indent(); + w.write("vv := &$T{}", memberSymbol); + w.write("*v = vv"); + w.write("return vv.Deserialize(d)"); + w.dedent(); + } + }; + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/UnionSerializer.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/UnionSerializer.java new file mode 100644 index 000000000..8487fd732 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde2/UnionSerializer.java @@ -0,0 +1,97 @@ +package software.amazon.smithy.go.codegen.serde2; + +import java.util.Comparator; +import java.util.List; +import software.amazon.smithy.codegen.core.CodegenException; +import software.amazon.smithy.codegen.core.Symbol; +import software.amazon.smithy.go.codegen.GoCodegenContext; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.SchemaGenerator; +import software.amazon.smithy.go.codegen.SmithyGoDependency; +import software.amazon.smithy.go.codegen.Writable; +import software.amazon.smithy.go.codegen.util.ShapeUtil; +import software.amazon.smithy.model.shapes.MemberShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.UnionShape; + +public class UnionSerializer implements Writable { + private final GoCodegenContext ctx; + private final UnionShape shape; + + public UnionSerializer(GoCodegenContext ctx, UnionShape shape) { + this.ctx = ctx; + this.shape = shape; + } + + @Override + public void accept(GoWriter writer) { + writer.addUseImports(SmithyGoDependency.SMITHY); + writer.addImport(ctx.settings().getModuleName() + "/schemas", "schemas"); + + var symbol = ctx.symbolProvider().toSymbol(shape); + var members = shape.members().stream() + .sorted(Comparator.comparing(MemberShape::getMemberName)) + .toList(); + + writer.openBlock("func serialize$L(s smithy.ShapeSerializer, schema *smithy.Schema, v $T) {", "}", + shape.getId().getName(), symbol, () -> { + writer.openBlock("switch vv := v.(type) {", "}", () -> { + renderCases(writer, members); + }); + }); + } + + private void renderCases(GoWriter writer, List members) { + for (var member : members) { + var variantSymbol = Symbol.builder() + .name(ctx.symbolProvider().toMemberName(member)) + .namespace(ctx.settings().getModuleName() + "/types", ".") + .build(); + var variantSchema = SchemaGenerator.getMemberSchemaRef(shape, member, ctx.service()); + var target = ctx.model().expectShape(member.getTarget()); + + writer.write("case *$T:", variantSymbol); + writer.indent(); + writer.write("s.WriteUnion(schema, $L)", variantSchema); + writeVariantValue(writer, target, variantSchema); + writer.write("s.CloseUnion()"); + writer.dedent(); + } + } + + private void writeVariantValue(GoWriter writer, Shape target, String schemaName) { + switch (target.getType()) { + case BYTE -> writer.write("s.WriteInt8($L, vv.Value)", schemaName); + case SHORT -> writer.write("s.WriteInt16($L, vv.Value)", schemaName); + case INTEGER -> writer.write("s.WriteInt32($L, vv.Value)", schemaName); + case LONG -> writer.write("s.WriteInt64($L, vv.Value)", schemaName); + case FLOAT -> writer.write("s.WriteFloat32($L, vv.Value)", schemaName); + case DOUBLE -> writer.write("s.WriteFloat64($L, vv.Value)", schemaName); + case BOOLEAN -> writer.write("s.WriteBool($L, vv.Value)", schemaName); + case STRING, ENUM -> { + if (ShapeUtil.isEnum(target)) { + writer.write("s.WriteString($L, string(vv.Value))", schemaName); + } else { + writer.write("s.WriteString($L, vv.Value)", schemaName); + } + } + case BLOB -> writer.write("s.WriteBlob($L, vv.Value)", schemaName); + case TIMESTAMP -> writer.write("s.WriteTime($L, vv.Value)", schemaName); + case INT_ENUM -> writer.write("s.WriteInt32($L, int32(vv.Value))", schemaName); + case BIG_INTEGER -> writer.write("s.WriteBigInt($L, vv.Value)", schemaName); + case BIG_DECIMAL -> writer.write("s.WriteBigFloat($L, vv.Value)", schemaName); + case STRUCTURE -> { + writer.write("s.WriteStruct($L)", schemaName); + writer.write("vv.Value.SerializeMembers(s)"); + writer.write("s.CloseStruct()"); + } + case LIST, SET, MAP -> writer.write("serialize$L(s, $L, vv.Value)", target.getId().getName(), schemaName); + case UNION -> writer.write("serialize$L(s, $L, vv.Value)", target.getId().getName(), schemaName); + case DOCUMENT -> { + writer.addUseImports(SmithyGoDependency.SMITHY_DOCUMENT); + writer.write("s.WriteDocument($L, &smithydocument.Opaque{Value: vv.Value})", schemaName); + } + case MEMBER, SERVICE, RESOURCE, OPERATION -> throw new CodegenException("invalid shape type " + target.getType()); + } + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/util/ShapeUtil.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/util/ShapeUtil.java index 1a561c1ca..03862a5a8 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/util/ShapeUtil.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/util/ShapeUtil.java @@ -15,15 +15,22 @@ package software.amazon.smithy.go.codegen.util; +import java.util.Set; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.knowledge.TopDownIndex; +import software.amazon.smithy.model.neighbor.Walker; import software.amazon.smithy.model.shapes.BooleanShape; import software.amazon.smithy.model.shapes.CollectionShape; import software.amazon.smithy.model.shapes.IntegerShape; import software.amazon.smithy.model.shapes.ListShape; import software.amazon.smithy.model.shapes.MapShape; import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.ShapeType; import software.amazon.smithy.model.shapes.StringShape; +import software.amazon.smithy.model.shapes.StructureShape; +import software.amazon.smithy.model.traits.EnumTrait; +import software.amazon.smithy.model.traits.UnitTypeTrait; public final class ShapeUtil { public static final StringShape STRING_SHAPE = StringShape.builder() @@ -38,8 +45,20 @@ public final class ShapeUtil { .id("smithy.api#PrimitiveBoolean") .build(); + public static final StructureShape UNIT = StructureShape.builder() + .id("smithy.api#Unit") + .addTrait(new UnitTypeTrait()) + .build(); + private ShapeUtil() {} + public static boolean isExported(Shape shape) { + return switch (shape.getType()) { + case OPERATION, STRUCTURE, UNION, ENUM -> true; + default -> false; + }; + } + public static ListShape listOf(Shape member) { return ListShape.builder() .id("smithy.go.synthetic#" + member.getId().getName() + "List") @@ -64,4 +83,10 @@ public static Shape expectMember(Model model, CollectionShape shape) { public static Shape expectMember(Model model, MapShape shape) { return model.expectShape(shape.getValue().getTarget()); } + + // Returns true for both IDL2 enum shapes and IDL1 string + @enum shapes. + public static boolean isEnum(Shape shape) { + return shape.getType() == ShapeType.ENUM + || (shape.getType() == ShapeType.STRING && shape.hasTrait(EnumTrait.class)); + } } diff --git a/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/TestUtils.java b/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/TestUtils.java index 41e32074c..3b1d2fdb4 100644 --- a/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/TestUtils.java +++ b/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/TestUtils.java @@ -109,6 +109,7 @@ public static ObjectNode getSettingsNode( .withMember("generateGoMod", Node.from(generateGoMod)) .withMember("homepage", Node.from("https://docs.amplify.aws/")) .withMember("sdkId", Node.from(sdkId)) + .withMember("useLegacySerde", Node.from(true)) .withMember("author", Node.from("Amazon Web Services")) .withMember("gitRepo", Node.from("https://github.com/aws-amplify/amplify-codegen.git")) .withMember("swiftVersion", Node.from("5.5.0")) diff --git a/document/document.go b/document/document.go index 8f852d95c..82b48eb59 100644 --- a/document/document.go +++ b/document/document.go @@ -4,6 +4,7 @@ import ( "fmt" "math/big" "strconv" + "time" ) // Marshaler is an interface for a type that marshals a document to its protocol-specific byte representation and @@ -15,26 +16,26 @@ import ( // When defining struct types. the `document` struct tag can be used to control how the value will be // marshaled into the resulting protocol document. // -// // Field is ignored -// Field int `document:"-"` +// // Field is ignored +// Field int `document:"-"` // -// // Field object of key "myName" -// Field int `document:"myName"` +// // Field object of key "myName" +// Field int `document:"myName"` // -// // Field object key of key "myName", and -// // Field is omitted if the field is a zero value for the type. -// Field int `document:"myName,omitempty"` +// // Field object key of key "myName", and +// // Field is omitted if the field is a zero value for the type. +// Field int `document:"myName,omitempty"` // -// // Field object key of "Field", and -// // Field is omitted if the field is a zero value for the type. -// Field int `document:",omitempty"` +// // Field object key of "Field", and +// // Field is omitted if the field is a zero value for the type. +// Field int `document:",omitempty"` // // All struct fields, including anonymous fields, are marshaled unless the // any of the following conditions are meet. // -// - the field is not exported -// - document field tag is "-" -// - document field tag specifies "omitempty", and is a zero value. +// - the field is not exported +// - document field tag is "-" +// - document field tag specifies "omitempty", and is a zero value. // // Pointer and interface values are encoded as the value pointed to or // contained in the interface. A nil value encodes as a null @@ -50,6 +51,13 @@ import ( // // Marshal cannot represent cyclic data structures and will not handle them. // Passing cyclic structures to Marshal will result in an infinite recursion. +// +// Marshaler is not used in schema-serde based services (which are currently +// being rolled out) since having an implementation of Marshaler locks a +// document into support for a specific serial format. Existing implementations +// of Marshaler will continue to encode to JSON as that is effectively the only +// serial format supported for Document prior to the introduction of +// schema-serde. In schema-serde services it is replaced by [Value]. type Marshaler interface { MarshalSmithyDocument() ([]byte, error) } @@ -63,18 +71,94 @@ type Marshaler interface { // // Both generic interface{} and concrete types are valid unmarshal destination types. When unmarshaling a document // into an empty interface the Unmarshaler will store one of these values: -// bool, for boolean values -// document.Number, for arbitrary-precision numbers (int64, float64, big.Int, big.Float) -// string, for string values -// []interface{}, for array values -// map[string]interface{}, for objects -// nil, for null values +// +// bool, for boolean values +// document.Number, for arbitrary-precision numbers (int64, float64, big.Int, big.Float) +// string, for string values +// []interface{}, for array values +// map[string]interface{}, for objects +// nil, for null values // // When unmarshaling, any error that occurs will halt the unmarshal and return the error. type Unmarshaler interface { UnmarshalSmithyDocument(v interface{}) error } +// Value is a sealed type representing a Smithy document value. It covers the +// full Smithy data model including blob and timestamp. +// +// The following types implement Value: +// - [Null] +// - [Boolean] +// - [Number] +// - [String] +// - [Blob] +// - [Timestamp] +// - [List] +// - [Map] +// - [Structure] +// - [Opaque] +type Value interface { + isValue() +} + +// Null is a document null value. +type Null struct{} + +func (Null) isValue() {} + +// Boolean is a document boolean value. +type Boolean bool + +func (Boolean) isValue() {} + +// String is a document string value. +type String string + +func (String) isValue() {} + +// Blob is a document blob value. +type Blob []byte + +func (Blob) isValue() {} + +// Timestamp is a document timestamp value. +type Timestamp time.Time + +func (Timestamp) isValue() {} + +// List is a document list value. +type List []Value + +func (List) isValue() {} + +// Map is a document map value with string keys. +type Map map[string]Value + +func (Map) isValue() {} + +// Structure is a document structure value with an optional discriminator +// identifying the shape it represents. +type Structure struct { + // Discriminator is the absolute shape ID (e.g. + // "com.example#MyShape") of the concrete type this structure + // represents. It may be empty if the type is unknown. + Discriminator string + + // Members maps member names to their document values. + Members map[string]Value +} + +func (Structure) isValue() {} + +// Opaque wraps an arbitrary Go value for backward compatibility with the +// legacy reflection-based document serialization path. +type Opaque struct { + Value any +} + +func (Opaque) isValue() {} + type noSerde interface { noSmithyDocumentSerde() } @@ -96,6 +180,8 @@ func IsNoSerde(x interface{}) bool { // Number is an arbitrary precision numerical value type Number string +func (Number) isValue() {} + // Int64 returns the number as a string. func (n Number) String() string { return string(n) diff --git a/eventstream/const.go b/eventstream/const.go new file mode 100644 index 000000000..893156c5d --- /dev/null +++ b/eventstream/const.go @@ -0,0 +1,24 @@ +package eventstream + +// EventStream headers with specific meaning to async API functionality. +const ( + ChunkSignatureHeader = `:chunk-signature` // chunk signature for message + DateHeader = `:date` // Date header for signature + ContentTypeHeader = ":content-type" // message payload content-type + + // Message header and values + MessageTypeHeader = `:message-type` // Identifies type of message. + EventMessageType = `event` + ErrorMessageType = `error` + ExceptionMessageType = `exception` + + // Message Events + EventTypeHeader = `:event-type` // Identifies message event type e.g. "Stats". + + // Message Error + ErrorCodeHeader = `:error-code` + ErrorMessageHeader = `:error-message` + + // Message Exception + ExceptionTypeHeader = `:exception-type` +) diff --git a/eventstream/debug.go b/eventstream/debug.go new file mode 100644 index 000000000..6049402b1 --- /dev/null +++ b/eventstream/debug.go @@ -0,0 +1,144 @@ +package eventstream + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "strconv" +) + +type decodedMessage struct { + rawMessage + Headers decodedHeaders `json:"headers"` +} +type jsonMessage struct { + Length json.Number `json:"total_length"` + HeadersLen json.Number `json:"headers_length"` + PreludeCRC json.Number `json:"prelude_crc"` + Headers decodedHeaders `json:"headers"` + Payload []byte `json:"payload"` + CRC json.Number `json:"message_crc"` +} + +func (d *decodedMessage) UnmarshalJSON(b []byte) (err error) { + var jsonMsg jsonMessage + if err = json.Unmarshal(b, &jsonMsg); err != nil { + return err + } + + d.Length, err = numAsUint32(jsonMsg.Length) + if err != nil { + return err + } + d.HeadersLen, err = numAsUint32(jsonMsg.HeadersLen) + if err != nil { + return err + } + d.PreludeCRC, err = numAsUint32(jsonMsg.PreludeCRC) + if err != nil { + return err + } + d.Headers = jsonMsg.Headers + d.Payload = jsonMsg.Payload + d.CRC, err = numAsUint32(jsonMsg.CRC) + if err != nil { + return err + } + + return nil +} + +func (d *decodedMessage) MarshalJSON() ([]byte, error) { + jsonMsg := jsonMessage{ + Length: json.Number(strconv.Itoa(int(d.Length))), + HeadersLen: json.Number(strconv.Itoa(int(d.HeadersLen))), + PreludeCRC: json.Number(strconv.Itoa(int(d.PreludeCRC))), + Headers: d.Headers, + Payload: d.Payload, + CRC: json.Number(strconv.Itoa(int(d.CRC))), + } + + return json.Marshal(jsonMsg) +} + +func numAsUint32(n json.Number) (uint32, error) { + v, err := n.Int64() + if err != nil { + return 0, fmt.Errorf("failed to get int64 json number, %v", err) + } + + return uint32(v), nil +} + +func (d decodedMessage) Message() Message { + return Message{ + Headers: Headers(d.Headers), + Payload: d.Payload, + } +} + +type decodedHeaders Headers + +func (hs *decodedHeaders) UnmarshalJSON(b []byte) error { + var jsonHeaders []struct { + Name string `json:"name"` + Type valueType `json:"type"` + Value any `json:"value"` + } + + decoder := json.NewDecoder(bytes.NewReader(b)) + decoder.UseNumber() + if err := decoder.Decode(&jsonHeaders); err != nil { + return err + } + + var headers Headers + for _, h := range jsonHeaders { + value, err := valueFromType(h.Type, h.Value) + if err != nil { + return err + } + headers.Set(h.Name, value) + } + *hs = decodedHeaders(headers) + + return nil +} + +func valueFromType(typ valueType, val any) (Value, error) { + switch typ { + case trueValueType: + return BoolValue(true), nil + case falseValueType: + return BoolValue(false), nil + case int8ValueType: + v, err := val.(json.Number).Int64() + return Int8Value(int8(v)), err + case int16ValueType: + v, err := val.(json.Number).Int64() + return Int16Value(int16(v)), err + case int32ValueType: + v, err := val.(json.Number).Int64() + return Int32Value(int32(v)), err + case int64ValueType: + v, err := val.(json.Number).Int64() + return Int64Value(v), err + case bytesValueType: + v, err := base64.StdEncoding.DecodeString(val.(string)) + return BytesValue(v), err + case stringValueType: + v, err := base64.StdEncoding.DecodeString(val.(string)) + return StringValue(string(v)), err + case timestampValueType: + v, err := val.(json.Number).Int64() + return TimestampValue(timeFromEpochMilli(v)), err + case uuidValueType: + v, err := base64.StdEncoding.DecodeString(val.(string)) + var tv UUIDValue + copy(tv[:], v) + return tv, err + default: + panic(fmt.Sprintf("unknown type, %s, %T", typ.String(), val)) + } +} diff --git a/eventstream/decode.go b/eventstream/decode.go new file mode 100644 index 000000000..d9ab7652f --- /dev/null +++ b/eventstream/decode.go @@ -0,0 +1,218 @@ +package eventstream + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "encoding/json" + "fmt" + "github.com/aws/smithy-go/logging" + "hash" + "hash/crc32" + "io" +) + +// DecoderOptions is the Decoder configuration options. +type DecoderOptions struct { + Logger logging.Logger + LogMessages bool +} + +// Decoder provides decoding of an Event Stream messages. +type Decoder struct { + options DecoderOptions +} + +// NewDecoder initializes and returns a Decoder for decoding event +// stream messages from the reader provided. +func NewDecoder(optFns ...func(*DecoderOptions)) *Decoder { + options := DecoderOptions{} + + for _, fn := range optFns { + fn(&options) + } + + return &Decoder{ + options: options, + } +} + +// Decode attempts to decode a single message from the event stream reader. +// Will return the event stream message, or error if decodeMessage fails to read +// the message from the stream. +// +// payloadBuf is a byte slice that will be used in the returned Message.Payload. Callers +// must ensure that the Message.Payload from a previous decode has been consumed before passing in the same underlying +// payloadBuf byte slice. +func (d *Decoder) Decode(reader io.Reader, payloadBuf []byte) (m Message, err error) { + if d.options.Logger != nil && d.options.LogMessages { + debugMsgBuf := bytes.NewBuffer(nil) + reader = io.TeeReader(reader, debugMsgBuf) + defer func() { + logMessageDecode(d.options.Logger, debugMsgBuf, m, err) + }() + } + + m, err = decodeMessage(reader, payloadBuf) + + return m, err +} + +// decodeMessage attempts to decode a single message from the event stream reader. +// Will return the event stream message, or error if decodeMessage fails to read +// the message from the reader. +func decodeMessage(reader io.Reader, payloadBuf []byte) (m Message, err error) { + crc := crc32.New(crc32IEEETable) + hashReader := io.TeeReader(reader, crc) + + prelude, err := decodePrelude(hashReader, crc) + if err != nil { + return Message{}, err + } + + if prelude.HeadersLen > 0 { + lr := io.LimitReader(hashReader, int64(prelude.HeadersLen)) + m.Headers, err = decodeHeaders(lr) + if err != nil { + return Message{}, err + } + } + + if payloadLen := prelude.PayloadLen(); payloadLen > 0 { + buf, err := decodePayload(payloadBuf, io.LimitReader(hashReader, int64(payloadLen))) + if err != nil { + return Message{}, err + } + m.Payload = buf + } + + msgCRC := crc.Sum32() + if err := validateCRC(reader, msgCRC); err != nil { + return Message{}, err + } + + return m, nil +} + +func logMessageDecode(logger logging.Logger, msgBuf *bytes.Buffer, msg Message, decodeErr error) { + w := bytes.NewBuffer(nil) + defer func() { logger.Logf(logging.Debug, w.String()) }() + + fmt.Fprintf(w, "Raw message:\n%s\n", + hex.Dump(msgBuf.Bytes())) + + if decodeErr != nil { + fmt.Fprintf(w, "decodeMessage error: %v\n", decodeErr) + return + } + + rawMsg, err := msg.rawMessage() + if err != nil { + fmt.Fprintf(w, "failed to create raw message, %v\n", err) + return + } + + decodedMsg := decodedMessage{ + rawMessage: rawMsg, + Headers: decodedHeaders(msg.Headers), + } + + fmt.Fprintf(w, "Decoded message:\n") + encoder := json.NewEncoder(w) + if err := encoder.Encode(decodedMsg); err != nil { + fmt.Fprintf(w, "failed to generate decoded message, %v\n", err) + } +} + +func decodePrelude(r io.Reader, crc hash.Hash32) (messagePrelude, error) { + var p messagePrelude + + var err error + p.Length, err = decodeUint32(r) + if err != nil { + return messagePrelude{}, err + } + + p.HeadersLen, err = decodeUint32(r) + if err != nil { + return messagePrelude{}, err + } + + if err := p.ValidateLens(); err != nil { + return messagePrelude{}, err + } + + preludeCRC := crc.Sum32() + if err := validateCRC(r, preludeCRC); err != nil { + return messagePrelude{}, err + } + + p.PreludeCRC = preludeCRC + + return p, nil +} + +func decodePayload(buf []byte, r io.Reader) ([]byte, error) { + w := bytes.NewBuffer(buf[0:0]) + + _, err := io.Copy(w, r) + return w.Bytes(), err +} + +func decodeUint8(r io.Reader) (uint8, error) { + type byteReader interface { + ReadByte() (byte, error) + } + + if br, ok := r.(byteReader); ok { + v, err := br.ReadByte() + return v, err + } + + var b [1]byte + _, err := io.ReadFull(r, b[:]) + return b[0], err +} + +func decodeUint16(r io.Reader) (uint16, error) { + var b [2]byte + bs := b[:] + _, err := io.ReadFull(r, bs) + if err != nil { + return 0, err + } + return binary.BigEndian.Uint16(bs), nil +} + +func decodeUint32(r io.Reader) (uint32, error) { + var b [4]byte + bs := b[:] + _, err := io.ReadFull(r, bs) + if err != nil { + return 0, err + } + return binary.BigEndian.Uint32(bs), nil +} + +func decodeUint64(r io.Reader) (uint64, error) { + var b [8]byte + bs := b[:] + _, err := io.ReadFull(r, bs) + if err != nil { + return 0, err + } + return binary.BigEndian.Uint64(bs), nil +} + +func validateCRC(r io.Reader, expect uint32) error { + msgCRC, err := decodeUint32(r) + if err != nil { + return err + } + + if msgCRC != expect { + return ChecksumError{} + } + + return nil +} diff --git a/eventstream/decode_test.go b/eventstream/decode_test.go new file mode 100644 index 000000000..e73044d46 --- /dev/null +++ b/eventstream/decode_test.go @@ -0,0 +1,188 @@ +package eventstream + +import ( + "bytes" + "encoding/hex" + "errors" + "io/ioutil" + "os" + "reflect" + "testing" +) + +func TestWriteEncodedFromDecoded(t *testing.T) { + cases, err := readPositiveTests("testdata") + if err != nil { + t.Fatalf("failed to load positive tests, %v", err) + } + + for _, c := range cases { + f, err := ioutil.TempFile(os.TempDir(), "encoded_positive_"+c.Name) + if err != nil { + t.Fatalf("failed to open %q, %v", c.Name, err) + } + + encoder := NewEncoder() + + msg := c.Decoded.Message() + if err := encoder.Encode(f, msg); err != nil { + t.Errorf("failed to encode %q, %v", c.Name, err) + } + + if err = f.Close(); err != nil { + t.Errorf("expected %v, got %v", "no error", err) + } + if err = os.Remove(f.Name()); err != nil { + t.Errorf("expected %v, got %v", "no error", err) + } + } +} + +func TestDecoder_Decode(t *testing.T) { + cases, err := readPositiveTests("testdata") + if err != nil { + t.Fatalf("failed to load positive tests, %v", err) + } + + for _, c := range cases { + decoder := NewDecoder() + + msg, err := decoder.Decode(bytes.NewBuffer(c.Encoded), nil) + if err != nil { + t.Fatalf("%s, expect no decode error, got %v", c.Name, err) + } + + raw, err := msg.rawMessage() // rawMessage will fail if payload read CRC fails + if err != nil { + t.Fatalf("%s, failed to get raw decoded message %v", c.Name, err) + } + + if e, a := c.Decoded.Length, raw.Length; e != a { + t.Errorf("%s, expect %v length, got %v", c.Name, e, a) + } + if e, a := c.Decoded.HeadersLen, raw.HeadersLen; e != a { + t.Errorf("%s, expect %v HeadersLen, got %v", c.Name, e, a) + } + if e, a := c.Decoded.PreludeCRC, raw.PreludeCRC; e != a { + t.Errorf("%s, expect %v PreludeCRC, got %v", c.Name, e, a) + } + if e, a := Headers(c.Decoded.Headers), msg.Headers; !reflect.DeepEqual(e, a) { + t.Errorf("%s, expect %v headers, got %v", c.Name, e, a) + } + if e, a := c.Decoded.Payload, raw.Payload; !bytes.Equal(e, a) { + t.Errorf("%s, expect %v payload, got %v", c.Name, e, a) + } + if e, a := c.Decoded.CRC, raw.CRC; e != a { + t.Errorf("%s, expect %v CRC, got %v", c.Name, e, a) + } + } +} + +func TestDecoder_Decode_Negative(t *testing.T) { + cases, err := readNegativeTests("testdata") + if err != nil { + t.Fatalf("failed to load negative tests, %v", err) + } + + for _, c := range cases { + decoder := NewDecoder() + + msg, err := decoder.Decode(bytes.NewBuffer(c.Encoded), nil) + if err == nil { + rawMsg, rawMsgErr := msg.rawMessage() + t.Fatalf("%s, expect error, got none, %s,\n%s\n%#v, %v\n", c.Name, + c.Err, hex.Dump(c.Encoded), rawMsg, rawMsgErr) + } + } +} + +var testEncodedMsg = []byte{0, 0, 0, 61, 0, 0, 0, 32, 7, 253, 131, 150, 12, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 16, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 106, 115, 111, 110, 123, 39, 102, 111, 111, 39, 58, 39, 98, 97, 114, 39, 125, 141, 156, 8, 177} + +func TestDecoder_DecodeMultipleMessages(t *testing.T) { + const ( + expectMsgCount = 10 + expectPayloadLen = 13 + ) + + r := bytes.NewBuffer(nil) + for range expectMsgCount { + r.Write(testEncodedMsg) + } + + decoder := NewDecoder() + + var err error + var msg Message + var count int + for { + msg, err = decoder.Decode(r, nil) + if err != nil { + break + } + count++ + + if e, a := expectPayloadLen, len(msg.Payload); e != a { + t.Errorf("expect %v payload len, got %v", e, a) + } + + if e, a := []byte(`{'foo':'bar'}`), msg.Payload; !bytes.Equal(e, a) { + t.Errorf("expect %v payload, got %v", e, a) + } + } + + type causer interface { + Cause() error + } + if err != nil && count != expectMsgCount { + t.Fatalf("expect, no error, got %v", err) + } + + if e, a := expectMsgCount, count; e != a { + t.Errorf("expect %v messages read, got %v", e, a) + } +} + +func TestDecodeLimits(t *testing.T) { + l := 25 * 1024 * 1024 // Previously we failed if message was set to >16 MB + payload := bytes.Repeat([]byte{0x01}, l) // if set to just 0, message will be read as having 0 size. + buffer := bytes.NewBuffer(payload) + _, err := NewDecoder().Decode(buffer, nil) + if err == nil { + t.Fatalf("expect error since message is not properly encoded, got none") + } + if errors.As(err, &LengthError{}) { + t.Fatalf("expect error not being a length error, got %v", err) + } +} + +func BenchmarkDecode(b *testing.B) { + r := bytes.NewReader(testEncodedMsg) + decoder := NewDecoder() + payloadBuf := make([]byte, 0, 5*1024) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + msg, err := decoder.Decode(r, payloadBuf) + if err != nil { + b.Fatal(err) + } + + // Release the payload buffer + payloadBuf = msg.Payload[0:0] + r.Seek(0, 0) + } +} + +func BenchmarkDecode_NoPayloadBuf(b *testing.B) { + r := bytes.NewReader(testEncodedMsg) + decoder := NewDecoder() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _, err := decoder.Decode(r, nil) + if err != nil { + b.Fatal(err) + } + r.Seek(0, 0) + } +} diff --git a/eventstream/deserializer.go b/eventstream/deserializer.go new file mode 100644 index 000000000..8bc931a32 --- /dev/null +++ b/eventstream/deserializer.go @@ -0,0 +1,294 @@ +package eventstream + +import ( + "fmt" + "math/big" + "time" + + "github.com/aws/smithy-go" + "github.com/aws/smithy-go/document" + "github.com/aws/smithy-go/traits" +) + +// ShapeDeserializer wraps a [smithy.ShapeDeserializer] to handle event stream +// message binding traits. +type ShapeDeserializer struct { + Message *Message + + inner smithy.ShapeDeserializer + + depth int + schema *smithy.Schema + + bindings []*smithy.Schema + bindIdx int + inBindings bool + + inBody bool + hasPayload bool + hasBody bool +} + +var _ smithy.ShapeDeserializer = (*ShapeDeserializer)(nil) + +// NewShapeDeserializer returns a deserializer for a Message. +func NewShapeDeserializer(msg *Message, inner smithy.ShapeDeserializer) *ShapeDeserializer { + return &ShapeDeserializer{ + Message: msg, + inner: inner, + } +} + +func (d *ShapeDeserializer) ReadStruct(s *smithy.Schema) error { + d.depth++ + if d.depth > 1 { + return d.inner.ReadStruct(s) + } + d.schema = s + for _, m := range s.Members() { + if _, ok := smithy.SchemaTrait[*traits.EventPayload](m); ok { + d.hasPayload = true + } + if isEventBound(m) { + d.bindings = append(d.bindings, m) + } else { + d.hasBody = true + } + } + return nil +} + +func (d *ShapeDeserializer) ReadStructMember() (*smithy.Schema, error) { + if d.depth > 1 { + ms, err := d.inner.ReadStructMember() + if ms == nil { + d.depth-- + } + return ms, err + } + + // like httpbinding, throw back the bound stuff first before we drop into + // the body + for d.bindIdx < len(d.bindings) { + m := d.bindings[d.bindIdx] + d.bindIdx++ + if isEventHeader(m) && d.Message.Headers.Get(m.MemberName()) == nil { + continue + } + d.inBindings = true + return m, nil + } + d.inBindings = false + + if d.hasPayload { + d.depth-- + return nil, nil + } + + if !d.hasBody { + d.depth-- + return nil, nil + } + + if !d.inBody { + d.inBody = true + if err := d.inner.ReadStruct(d.schema); err != nil { + return nil, err + } + } + + ms, err := d.inner.ReadStructMember() + if ms == nil { + d.depth-- + } + + return ms, err +} + +func (d *ShapeDeserializer) ReadString(s *smithy.Schema, v *string) error { + if d.inBindings { + if isEventHeader(s) { + hv := d.Message.Headers.Get(s.MemberName()) + if hv == nil { + return nil + } + sv, ok := hv.(StringValue) + if !ok { + return fmt.Errorf("event header %q: expected string, got %T", s.MemberName(), hv) + } + *v = string(sv) + return nil + } + if isEventPayload(s) { + *v = string(d.Message.Payload) + return nil + } + } + return d.inner.ReadString(s, v) +} + +func (d *ShapeDeserializer) ReadBool(s *smithy.Schema, v *bool) error { + if d.inBindings && isEventHeader(s) { + hv := d.Message.Headers.Get(s.MemberName()) + if hv == nil { + return nil + } + bv, ok := hv.(BoolValue) + if !ok { + return fmt.Errorf("event header %q: expected bool, got %T", s.MemberName(), hv) + } + *v = bool(bv) + return nil + } + return d.inner.ReadBool(s, v) +} + +func (d *ShapeDeserializer) readHeaderInt64(name string) (int64, bool, error) { + hv := d.Message.Headers.Get(name) + if hv == nil { + return 0, false, nil + } + switch v := hv.(type) { + case Int8Value: + return int64(v), true, nil + case Int16Value: + return int64(v), true, nil + case Int32Value: + return int64(v), true, nil + case Int64Value: + return int64(v), true, nil + default: + return 0, false, fmt.Errorf("event header %q: expected integer, got %T", name, hv) + } +} + +type intn interface { + int8 | int16 | int32 | int64 +} + +func readEventHeaderInt[T intn](d *ShapeDeserializer, s *smithy.Schema, v *T) error { + n, ok, err := d.readHeaderInt64(s.MemberName()) + if err != nil || !ok { + return err + } + *v = T(n) + return nil +} + +func (d *ShapeDeserializer) ReadInt8(s *smithy.Schema, v *int8) error { + if d.inBindings && isEventHeader(s) { + return readEventHeaderInt(d, s, v) + } + return d.inner.ReadInt8(s, v) +} + +func (d *ShapeDeserializer) ReadInt16(s *smithy.Schema, v *int16) error { + if d.inBindings && isEventHeader(s) { + return readEventHeaderInt(d, s, v) + } + return d.inner.ReadInt16(s, v) +} + +func (d *ShapeDeserializer) ReadInt32(s *smithy.Schema, v *int32) error { + if d.inBindings && isEventHeader(s) { + return readEventHeaderInt(d, s, v) + } + return d.inner.ReadInt32(s, v) +} + +func (d *ShapeDeserializer) ReadInt64(s *smithy.Schema, v *int64) error { + if d.inBindings && isEventHeader(s) { + return readEventHeaderInt(d, s, v) + } + return d.inner.ReadInt64(s, v) +} + +func (d *ShapeDeserializer) ReadFloat32(s *smithy.Schema, v *float32) error { + return d.inner.ReadFloat32(s, v) +} + +func (d *ShapeDeserializer) ReadFloat64(s *smithy.Schema, v *float64) error { + return d.inner.ReadFloat64(s, v) +} + +func (d *ShapeDeserializer) ReadBlob(s *smithy.Schema, v *[]byte) error { + if d.inBindings { + if isEventHeader(s) { + hv := d.Message.Headers.Get(s.MemberName()) + if hv == nil { + return nil + } + bv, ok := hv.(BytesValue) + if !ok { + return fmt.Errorf("event header %q: expected bytes, got %T", s.MemberName(), hv) + } + *v = []byte(bv) + return nil + } + if isEventPayload(s) { + *v = d.Message.Payload + return nil + } + } + return d.inner.ReadBlob(s, v) +} + +func (d *ShapeDeserializer) ReadTime(s *smithy.Schema, v *time.Time) error { + if d.inBindings && isEventHeader(s) { + hv := d.Message.Headers.Get(s.MemberName()) + if hv == nil { + return nil + } + tv, ok := hv.(TimestampValue) + if !ok { + return fmt.Errorf("event header %q: expected timestamp, got %T", s.MemberName(), hv) + } + *v = time.Time(tv) + return nil + } + return d.inner.ReadTime(s, v) +} + +func (d *ShapeDeserializer) ReadList(s *smithy.Schema) error { + return d.inner.ReadList(s) +} + +func (d *ShapeDeserializer) ReadListItem(s *smithy.Schema) (bool, error) { + return d.inner.ReadListItem(s) +} + +func (d *ShapeDeserializer) ReadMap(s *smithy.Schema) error { + return d.inner.ReadMap(s) +} + +func (d *ShapeDeserializer) ReadMapKey(s *smithy.Schema) (string, bool, error) { + return d.inner.ReadMapKey(s) +} + +func (d *ShapeDeserializer) ReadUnion(s *smithy.Schema) (*smithy.Schema, error) { + return d.inner.ReadUnion(s) +} + +func (d *ShapeDeserializer) ReadNil(s *smithy.Schema) (bool, error) { + return d.inner.ReadNil(s) +} + +func (d *ShapeDeserializer) ReadDocument(s *smithy.Schema, v *document.Value) error { + return d.inner.ReadDocument(s, v) +} + +func isEventBound(schema *smithy.Schema) bool { + _, h := smithy.SchemaTrait[*traits.EventHeader](schema) + _, p := smithy.SchemaTrait[*traits.EventPayload](schema) + return h || p +} + +// ReadBigInt is unimplemented and will return an error. +func (d *ShapeDeserializer) ReadBigInt(_ *smithy.Schema, _ *big.Int) error { + return fmt.Errorf("unimplemented") +} + +// ReadBigFloat is unimplemented and will return an error. +func (d *ShapeDeserializer) ReadBigFloat(_ *smithy.Schema, _ *big.Float) error { + return fmt.Errorf("unimplemented") +} diff --git a/eventstream/encode.go b/eventstream/encode.go new file mode 100644 index 000000000..61cf7238d --- /dev/null +++ b/eventstream/encode.go @@ -0,0 +1,167 @@ +package eventstream + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "encoding/json" + "fmt" + "github.com/aws/smithy-go/logging" + "hash" + "hash/crc32" + "io" +) + +// EncoderOptions is the configuration options for Encoder. +type EncoderOptions struct { + Logger logging.Logger + LogMessages bool +} + +// Encoder provides EventStream message encoding. +type Encoder struct { + options EncoderOptions + + headersBuf *bytes.Buffer + messageBuf *bytes.Buffer +} + +// NewEncoder initializes and returns an Encoder to encode Event Stream +// messages. +func NewEncoder(optFns ...func(*EncoderOptions)) *Encoder { + o := EncoderOptions{} + + for _, fn := range optFns { + fn(&o) + } + + return &Encoder{ + options: o, + headersBuf: bytes.NewBuffer(nil), + messageBuf: bytes.NewBuffer(nil), + } +} + +// Encode encodes a single EventStream message to the io.Writer the Encoder +// was created with. An error is returned if writing the message fails. +func (e *Encoder) Encode(w io.Writer, msg Message) (err error) { + e.headersBuf.Reset() + e.messageBuf.Reset() + + var writer io.Writer = e.messageBuf + if e.options.Logger != nil && e.options.LogMessages { + encodeMsgBuf := bytes.NewBuffer(nil) + writer = io.MultiWriter(writer, encodeMsgBuf) + defer func() { + logMessageEncode(e.options.Logger, encodeMsgBuf, msg, err) + }() + } + + if err = EncodeHeaders(e.headersBuf, msg.Headers); err != nil { + return err + } + + crc := crc32.New(crc32IEEETable) + hashWriter := io.MultiWriter(writer, crc) + + headersLen := uint32(e.headersBuf.Len()) + payloadLen := uint32(len(msg.Payload)) + + if err = encodePrelude(hashWriter, crc, headersLen, payloadLen); err != nil { + return err + } + + if headersLen > 0 { + if _, err = io.Copy(hashWriter, e.headersBuf); err != nil { + return err + } + } + + if payloadLen > 0 { + if _, err = hashWriter.Write(msg.Payload); err != nil { + return err + } + } + + msgCRC := crc.Sum32() + if err := binary.Write(writer, binary.BigEndian, msgCRC); err != nil { + return err + } + + _, err = io.Copy(w, e.messageBuf) + + return err +} + +func logMessageEncode(logger logging.Logger, msgBuf *bytes.Buffer, msg Message, encodeErr error) { + w := bytes.NewBuffer(nil) + defer func() { logger.Logf(logging.Debug, w.String()) }() + + fmt.Fprintf(w, "Message to encode:\n") + encoder := json.NewEncoder(w) + if err := encoder.Encode(msg); err != nil { + fmt.Fprintf(w, "Failed to get encoded message, %v\n", err) + } + + if encodeErr != nil { + fmt.Fprintf(w, "Encode error: %v\n", encodeErr) + return + } + + fmt.Fprintf(w, "Raw message:\n%s\n", hex.Dump(msgBuf.Bytes())) +} + +func encodePrelude(w io.Writer, crc hash.Hash32, headersLen, payloadLen uint32) error { + p := messagePrelude{ + Length: minMsgLen + headersLen + payloadLen, + HeadersLen: headersLen, + } + if err := p.ValidateLens(); err != nil { + return err + } + + err := binaryWriteFields(w, binary.BigEndian, + p.Length, + p.HeadersLen, + ) + if err != nil { + return err + } + + p.PreludeCRC = crc.Sum32() + err = binary.Write(w, binary.BigEndian, p.PreludeCRC) + if err != nil { + return err + } + + return nil +} + +// EncodeHeaders writes the header values to the writer encoded in the event +// stream format. Returns an error if a header fails to encode. +func EncodeHeaders(w io.Writer, headers Headers) error { + for _, h := range headers { + hn := headerName{ + Len: uint8(len(h.Name)), + } + copy(hn.Name[:hn.Len], h.Name) + if err := hn.encode(w); err != nil { + return err + } + + if err := h.Value.encode(w); err != nil { + return err + } + } + + return nil +} + +func binaryWriteFields(w io.Writer, order binary.ByteOrder, vs ...any) error { + for _, v := range vs { + if err := binary.Write(w, order, v); err != nil { + return err + } + } + return nil +} diff --git a/eventstream/encode_test.go b/eventstream/encode_test.go new file mode 100644 index 000000000..6d38e7ade --- /dev/null +++ b/eventstream/encode_test.go @@ -0,0 +1,75 @@ +package eventstream + +import ( + "bytes" + "encoding/hex" + "io" + "reflect" + "testing" +) + +func TestEncoder_Encode(t *testing.T) { + cases, err := readPositiveTests("testdata") + if err != nil { + t.Fatalf("failed to load positive tests, %v", err) + } + + for _, c := range cases { + var w bytes.Buffer + encoder := NewEncoder() + + err = encoder.Encode(&w, c.Decoded.Message()) + if err != nil { + t.Fatalf("%s, failed to encode message, %v", c.Name, err) + } + + if e, a := c.Encoded, w.Bytes(); !reflect.DeepEqual(e, a) { + t.Errorf("%s, expect:\n%v\nactual:\n%v\n", c.Name, + hex.Dump(e), hex.Dump(a)) + } + } +} + +func BenchmarkEncode(b *testing.B) { + var w bytes.Buffer + encoder := NewEncoder() + msg := Message{ + Headers: Headers{ + {Name: "event-id", Value: Int16Value(123)}, + }, + Payload: []byte(`{"abc":123}`), + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + err := encoder.Encode(&w, msg) + if err != nil { + b.Fatal(err) + } + } +} + +func TestEncoder_Limits(t *testing.T) { + l := 25 * 1024 * 1024 // Previously we failed if message was set to >16 MB + payload := make([]byte, l) + encoder := NewEncoder() + err := encoder.Encode(io.Discard, Message{Payload: payload}) + if err != nil { + t.Fatalf("Expected encoder being able to encode %d size, failed with %v", l, err) + } + + h := Header{ + Name: "event-id", Value: Int16Value(123), + } + + headers := make(Headers, 0, 10_000) // Previously we failed if headers size was above a certain size + for range 10_000 { + headers = append(headers, h) + } + + err = encoder.Encode(io.Discard, Message{Headers: headers}) + if err != nil { + t.Fatalf("Expected encoder being able to encode %d size, failed with %v", l, err) + } +} diff --git a/eventstream/error.go b/eventstream/error.go new file mode 100644 index 000000000..7616214dd --- /dev/null +++ b/eventstream/error.go @@ -0,0 +1,23 @@ +package eventstream + +import "fmt" + +// LengthError provides the error for items being larger than a maximum length. +type LengthError struct { + Part string + Want int + Have int + Value any +} + +func (e LengthError) Error() string { + return fmt.Sprintf("%s length invalid, %d/%d, %v", + e.Part, e.Want, e.Have, e.Value) +} + +// ChecksumError provides the error for message checksum invalidation errors. +type ChecksumError struct{} + +func (e ChecksumError) Error() string { + return "message checksum mismatch" +} diff --git a/eventstream/header.go b/eventstream/header.go new file mode 100644 index 000000000..f580bda4c --- /dev/null +++ b/eventstream/header.go @@ -0,0 +1,175 @@ +package eventstream + +import ( + "encoding/binary" + "fmt" + "io" +) + +// Headers are a collection of EventStream header values. +type Headers []Header + +// Header is a single EventStream Key Value header pair. +type Header struct { + Name string + Value Value +} + +// Set associates the name with a value. If the header name already exists in +// the Headers the value will be replaced with the new one. +func (hs *Headers) Set(name string, value Value) { + var i int + for ; i < len(*hs); i++ { + if (*hs)[i].Name == name { + (*hs)[i].Value = value + return + } + } + + *hs = append(*hs, Header{ + Name: name, Value: value, + }) +} + +// Get returns the Value associated with the header. Nil is returned if the +// value does not exist. +func (hs Headers) Get(name string) Value { + for i := range hs { + if h := hs[i]; h.Name == name { + return h.Value + } + } + return nil +} + +// Del deletes the value in the Headers if it exists. +func (hs *Headers) Del(name string) { + for i := 0; i < len(*hs); i++ { + if (*hs)[i].Name == name { + copy((*hs)[i:], (*hs)[i+1:]) + (*hs) = (*hs)[:len(*hs)-1] + } + } +} + +// Clone returns a deep copy of the headers +func (hs Headers) Clone() Headers { + o := make(Headers, 0, len(hs)) + for _, h := range hs { + o.Set(h.Name, h.Value) + } + return o +} + +func decodeHeaders(r io.Reader) (Headers, error) { + hs := Headers{} + + for { + name, err := decodeHeaderName(r) + if err != nil { + if err == io.EOF { + // EOF while getting header name means no more headers + break + } + return nil, err + } + + value, err := decodeHeaderValue(r) + if err != nil { + return nil, err + } + + hs.Set(name, value) + } + + return hs, nil +} + +func decodeHeaderName(r io.Reader) (string, error) { + var n headerName + + var err error + n.Len, err = decodeUint8(r) + if err != nil { + return "", err + } + + name := n.Name[:n.Len] + if _, err := io.ReadFull(r, name); err != nil { + return "", err + } + + return string(name), nil +} + +func decodeHeaderValue(r io.Reader) (Value, error) { + var raw rawValue + + typ, err := decodeUint8(r) + if err != nil { + return nil, err + } + raw.Type = valueType(typ) + + var v Value + + switch raw.Type { + case trueValueType: + v = BoolValue(true) + case falseValueType: + v = BoolValue(false) + case int8ValueType: + var tv Int8Value + err = tv.decode(r) + v = tv + case int16ValueType: + var tv Int16Value + err = tv.decode(r) + v = tv + case int32ValueType: + var tv Int32Value + err = tv.decode(r) + v = tv + case int64ValueType: + var tv Int64Value + err = tv.decode(r) + v = tv + case bytesValueType: + var tv BytesValue + err = tv.decode(r) + v = tv + case stringValueType: + var tv StringValue + err = tv.decode(r) + v = tv + case timestampValueType: + var tv TimestampValue + err = tv.decode(r) + v = tv + case uuidValueType: + var tv UUIDValue + err = tv.decode(r) + v = tv + default: + panic(fmt.Sprintf("unknown value type %d", raw.Type)) + } + + // Error could be EOF, let caller deal with it + return v, err +} + +const maxHeaderNameLen = 255 + +type headerName struct { + Len uint8 + Name [maxHeaderNameLen]byte +} + +func (v headerName) encode(w io.Writer) error { + if err := binary.Write(w, binary.BigEndian, v.Len); err != nil { + return err + } + + _, err := w.Write(v.Name[:v.Len]) + return err +} diff --git a/eventstream/header_test.go b/eventstream/header_test.go new file mode 100644 index 000000000..d28096e43 --- /dev/null +++ b/eventstream/header_test.go @@ -0,0 +1,66 @@ +package eventstream + +import ( + "reflect" + "testing" + "time" +) + +func TestHeaders_Set(t *testing.T) { + expect := Headers{ + {Name: "ABC", Value: StringValue("123")}, + {Name: "EFG", Value: TimestampValue(time.Time{})}, + } + + var actual Headers + actual.Set("ABC", Int32Value(123)) + actual.Set("ABC", StringValue("123")) // replace case + actual.Set("EFG", TimestampValue(time.Time{})) + + if e, a := expect, actual; !reflect.DeepEqual(e, a) { + t.Errorf("expect %v headers, got %v", e, a) + } +} + +func TestHeaders_Get(t *testing.T) { + headers := Headers{ + {Name: "ABC", Value: StringValue("123")}, + {Name: "EFG", Value: TimestampValue(time.Time{})}, + } + + cases := []struct { + Name string + Value Value + }{ + {Name: "ABC", Value: StringValue("123")}, + {Name: "EFG", Value: TimestampValue(time.Time{})}, + {Name: "NotFound"}, + } + + for i, c := range cases { + actual := headers.Get(c.Name) + if e, a := c.Value, actual; !reflect.DeepEqual(e, a) { + t.Errorf("%d, expect %v value, got %v", i, e, a) + } + } +} + +func TestHeaders_Del(t *testing.T) { + headers := Headers{ + {Name: "ABC", Value: StringValue("123")}, + {Name: "EFG", Value: TimestampValue(time.Time{})}, + {Name: "HIJ", Value: StringValue("123")}, + {Name: "KML", Value: TimestampValue(time.Time{})}, + } + expectAfterDel := Headers{ + {Name: "EFG", Value: TimestampValue(time.Time{})}, + } + + headers.Del("HIJ") + headers.Del("ABC") + headers.Del("KML") + + if e, a := expectAfterDel, headers; !reflect.DeepEqual(e, a) { + t.Errorf("expect %v headers, got %v", e, a) + } +} diff --git a/eventstream/header_value.go b/eventstream/header_value.go new file mode 100644 index 000000000..61ed35366 --- /dev/null +++ b/eventstream/header_value.go @@ -0,0 +1,521 @@ +package eventstream + +import ( + "encoding/base64" + "encoding/binary" + "encoding/hex" + "fmt" + "io" + "strconv" + "time" +) + +const maxHeaderValueLen = 1<<15 - 1 // 2^15-1 or 32KB - 1 + +// valueType is the EventStream header value type. +type valueType uint8 + +// Header value types +const ( + trueValueType valueType = iota + falseValueType + int8ValueType // Byte + int16ValueType // Short + int32ValueType // Integer + int64ValueType // Long + bytesValueType + stringValueType + timestampValueType + uuidValueType +) + +func (t valueType) String() string { + switch t { + case trueValueType: + return "bool" + case falseValueType: + return "bool" + case int8ValueType: + return "int8" + case int16ValueType: + return "int16" + case int32ValueType: + return "int32" + case int64ValueType: + return "int64" + case bytesValueType: + return "byte_array" + case stringValueType: + return "string" + case timestampValueType: + return "timestamp" + case uuidValueType: + return "uuid" + default: + return fmt.Sprintf("unknown value type %d", uint8(t)) + } +} + +type rawValue struct { + Type valueType + Len uint16 // Only set for variable length slices + Value []byte // byte representation of value, BigEndian encoding. +} + +func (r rawValue) encodeScalar(w io.Writer, v any) error { + return binaryWriteFields(w, binary.BigEndian, + r.Type, + v, + ) +} + +func (r rawValue) encodeFixedSlice(w io.Writer, v []byte) error { + binary.Write(w, binary.BigEndian, r.Type) + + _, err := w.Write(v) + return err +} + +func (r rawValue) encodeBytes(w io.Writer, v []byte) error { + if len(v) > maxHeaderValueLen { + return LengthError{ + Part: "header value", + Want: maxHeaderValueLen, Have: len(v), + Value: v, + } + } + r.Len = uint16(len(v)) + + err := binaryWriteFields(w, binary.BigEndian, + r.Type, + r.Len, + ) + if err != nil { + return err + } + + _, err = w.Write(v) + return err +} + +func (r rawValue) encodeString(w io.Writer, v string) error { + if len(v) > maxHeaderValueLen { + return LengthError{ + Part: "header value", + Want: maxHeaderValueLen, Have: len(v), + Value: v, + } + } + r.Len = uint16(len(v)) + + type stringWriter interface { + WriteString(string) (int, error) + } + + err := binaryWriteFields(w, binary.BigEndian, + r.Type, + r.Len, + ) + if err != nil { + return err + } + + if sw, ok := w.(stringWriter); ok { + _, err = sw.WriteString(v) + } else { + _, err = w.Write([]byte(v)) + } + + return err +} + +func decodeFixedBytesValue(r io.Reader, buf []byte) error { + _, err := io.ReadFull(r, buf) + return err +} + +func decodeBytesValue(r io.Reader) ([]byte, error) { + var raw rawValue + var err error + raw.Len, err = decodeUint16(r) + if err != nil { + return nil, err + } + + buf := make([]byte, raw.Len) + _, err = io.ReadFull(r, buf) + if err != nil { + return nil, err + } + + return buf, nil +} + +func decodeStringValue(r io.Reader) (string, error) { + v, err := decodeBytesValue(r) + return string(v), err +} + +// Value represents the abstract header value. +type Value interface { + Get() any + String() string + valueType() valueType + encode(io.Writer) error +} + +// An BoolValue provides eventstream encoding, and representation +// of a Go bool value. +type BoolValue bool + +// Get returns the underlying type +func (v BoolValue) Get() any { + return bool(v) +} + +// valueType returns the EventStream header value type value. +func (v BoolValue) valueType() valueType { + if v { + return trueValueType + } + return falseValueType +} + +func (v BoolValue) String() string { + return strconv.FormatBool(bool(v)) +} + +// encode encodes the BoolValue into an eventstream binary value +// representation. +func (v BoolValue) encode(w io.Writer) error { + return binary.Write(w, binary.BigEndian, v.valueType()) +} + +// An Int8Value provides eventstream encoding, and representation of a Go +// int8 value. +type Int8Value int8 + +// Get returns the underlying value. +func (v Int8Value) Get() any { + return int8(v) +} + +// valueType returns the EventStream header value type value. +func (Int8Value) valueType() valueType { + return int8ValueType +} + +func (v Int8Value) String() string { + return fmt.Sprintf("0x%02x", int8(v)) +} + +// encode encodes the Int8Value into an eventstream binary value +// representation. +func (v Int8Value) encode(w io.Writer) error { + raw := rawValue{ + Type: v.valueType(), + } + + return raw.encodeScalar(w, v) +} + +func (v *Int8Value) decode(r io.Reader) error { + n, err := decodeUint8(r) + if err != nil { + return err + } + + *v = Int8Value(n) + return nil +} + +// An Int16Value provides eventstream encoding, and representation of a Go +// int16 value. +type Int16Value int16 + +// Get returns the underlying value. +func (v Int16Value) Get() any { + return int16(v) +} + +// valueType returns the EventStream header value type value. +func (Int16Value) valueType() valueType { + return int16ValueType +} + +func (v Int16Value) String() string { + return fmt.Sprintf("0x%04x", int16(v)) +} + +// encode encodes the Int16Value into an eventstream binary value +// representation. +func (v Int16Value) encode(w io.Writer) error { + raw := rawValue{ + Type: v.valueType(), + } + return raw.encodeScalar(w, v) +} + +func (v *Int16Value) decode(r io.Reader) error { + n, err := decodeUint16(r) + if err != nil { + return err + } + + *v = Int16Value(n) + return nil +} + +// An Int32Value provides eventstream encoding, and representation of a Go +// int32 value. +type Int32Value int32 + +// Get returns the underlying value. +func (v Int32Value) Get() any { + return int32(v) +} + +// valueType returns the EventStream header value type value. +func (Int32Value) valueType() valueType { + return int32ValueType +} + +func (v Int32Value) String() string { + return fmt.Sprintf("0x%08x", int32(v)) +} + +// encode encodes the Int32Value into an eventstream binary value +// representation. +func (v Int32Value) encode(w io.Writer) error { + raw := rawValue{ + Type: v.valueType(), + } + return raw.encodeScalar(w, v) +} + +func (v *Int32Value) decode(r io.Reader) error { + n, err := decodeUint32(r) + if err != nil { + return err + } + + *v = Int32Value(n) + return nil +} + +// An Int64Value provides eventstream encoding, and representation of a Go +// int64 value. +type Int64Value int64 + +// Get returns the underlying value. +func (v Int64Value) Get() any { + return int64(v) +} + +// valueType returns the EventStream header value type value. +func (Int64Value) valueType() valueType { + return int64ValueType +} + +func (v Int64Value) String() string { + return fmt.Sprintf("0x%016x", int64(v)) +} + +// encode encodes the Int64Value into an eventstream binary value +// representation. +func (v Int64Value) encode(w io.Writer) error { + raw := rawValue{ + Type: v.valueType(), + } + return raw.encodeScalar(w, v) +} + +func (v *Int64Value) decode(r io.Reader) error { + n, err := decodeUint64(r) + if err != nil { + return err + } + + *v = Int64Value(n) + return nil +} + +// An BytesValue provides eventstream encoding, and representation of a Go +// byte slice. +type BytesValue []byte + +// Get returns the underlying value. +func (v BytesValue) Get() any { + return []byte(v) +} + +// valueType returns the EventStream header value type value. +func (BytesValue) valueType() valueType { + return bytesValueType +} + +func (v BytesValue) String() string { + return base64.StdEncoding.EncodeToString([]byte(v)) +} + +// encode encodes the BytesValue into an eventstream binary value +// representation. +func (v BytesValue) encode(w io.Writer) error { + raw := rawValue{ + Type: v.valueType(), + } + + return raw.encodeBytes(w, []byte(v)) +} + +func (v *BytesValue) decode(r io.Reader) error { + buf, err := decodeBytesValue(r) + if err != nil { + return err + } + + *v = BytesValue(buf) + return nil +} + +// An StringValue provides eventstream encoding, and representation of a Go +// string. +type StringValue string + +// Get returns the underlying value. +func (v StringValue) Get() any { + return string(v) +} + +// valueType returns the EventStream header value type value. +func (StringValue) valueType() valueType { + return stringValueType +} + +func (v StringValue) String() string { + return string(v) +} + +// encode encodes the StringValue into an eventstream binary value +// representation. +func (v StringValue) encode(w io.Writer) error { + raw := rawValue{ + Type: v.valueType(), + } + + return raw.encodeString(w, string(v)) +} + +func (v *StringValue) decode(r io.Reader) error { + s, err := decodeStringValue(r) + if err != nil { + return err + } + + *v = StringValue(s) + return nil +} + +// An TimestampValue provides eventstream encoding, and representation of a Go +// timestamp. +type TimestampValue time.Time + +// Get returns the underlying value. +func (v TimestampValue) Get() any { + return time.Time(v) +} + +// valueType returns the EventStream header value type value. +func (TimestampValue) valueType() valueType { + return timestampValueType +} + +func (v TimestampValue) epochMilli() int64 { + nano := time.Time(v).UnixNano() + msec := nano / int64(time.Millisecond) + return msec +} + +func (v TimestampValue) String() string { + msec := v.epochMilli() + return strconv.FormatInt(msec, 10) +} + +// encode encodes the TimestampValue into an eventstream binary value +// representation. +func (v TimestampValue) encode(w io.Writer) error { + raw := rawValue{ + Type: v.valueType(), + } + + msec := v.epochMilli() + return raw.encodeScalar(w, msec) +} + +func (v *TimestampValue) decode(r io.Reader) error { + n, err := decodeUint64(r) + if err != nil { + return err + } + + *v = TimestampValue(timeFromEpochMilli(int64(n))) + return nil +} + +// MarshalJSON implements the json.Marshaler interface +func (v TimestampValue) MarshalJSON() ([]byte, error) { + return []byte(v.String()), nil +} + +func timeFromEpochMilli(t int64) time.Time { + secs := t / 1e3 + msec := t % 1e3 + return time.Unix(secs, msec*int64(time.Millisecond)).UTC() +} + +// An UUIDValue provides eventstream encoding, and representation of a UUID +// value. +type UUIDValue [16]byte + +// Get returns the underlying value. +func (v UUIDValue) Get() any { + return v[:] +} + +// valueType returns the EventStream header value type value. +func (UUIDValue) valueType() valueType { + return uuidValueType +} + +func (v UUIDValue) String() string { + var scratch [36]byte + + const dash = '-' + + hex.Encode(scratch[:8], v[0:4]) + scratch[8] = dash + hex.Encode(scratch[9:13], v[4:6]) + scratch[13] = dash + hex.Encode(scratch[14:18], v[6:8]) + scratch[18] = dash + hex.Encode(scratch[19:23], v[8:10]) + scratch[23] = dash + hex.Encode(scratch[24:], v[10:]) + + return string(scratch[:]) +} + +// encode encodes the UUIDValue into an eventstream binary value +// representation. +func (v UUIDValue) encode(w io.Writer) error { + raw := rawValue{ + Type: v.valueType(), + } + + return raw.encodeFixedSlice(w, v[:]) +} + +func (v *UUIDValue) decode(r io.Reader) error { + tv := (*v)[:] + return decodeFixedBytesValue(r, tv) +} diff --git a/eventstream/header_value_test.go b/eventstream/header_value_test.go new file mode 100644 index 000000000..065a9f3d1 --- /dev/null +++ b/eventstream/header_value_test.go @@ -0,0 +1,203 @@ +package eventstream + +import ( + "bytes" + "encoding/binary" + "io" + "reflect" + "testing" + "time" +) + +func binWrite(v any) []byte { + var w bytes.Buffer + binary.Write(&w, binary.BigEndian, v) + return w.Bytes() +} + +var testValueEncodingCases = []struct { + Val Value + Expect []byte + Decode func(io.Reader) (Value, error) +}{ + { + BoolValue(true), + []byte{byte(trueValueType)}, + nil, + }, + { + BoolValue(false), + []byte{byte(falseValueType)}, + nil, + }, + { + Int8Value(0x0f), + []byte{byte(int8ValueType), 0x0f}, + func(r io.Reader) (Value, error) { + var v Int8Value + err := v.decode(r) + return v, err + }, + }, + { + Int16Value(0x0f), + append([]byte{byte(int16ValueType)}, binWrite(int16(0x0f))...), + func(r io.Reader) (Value, error) { + var v Int16Value + err := v.decode(r) + return v, err + }, + }, + { + Int32Value(0x0f), + append([]byte{byte(int32ValueType)}, binWrite(int32(0x0f))...), + func(r io.Reader) (Value, error) { + var v Int32Value + err := v.decode(r) + return v, err + }, + }, + { + Int64Value(0x0f), + append([]byte{byte(int64ValueType)}, binWrite(int64(0x0f))...), + func(r io.Reader) (Value, error) { + var v Int64Value + err := v.decode(r) + return v, err + }, + }, + { + BytesValue([]byte{0, 1, 2, 3}), + []byte{byte(bytesValueType), 0x00, 0x04, 0, 1, 2, 3}, + func(r io.Reader) (Value, error) { + var v BytesValue + err := v.decode(r) + return v, err + }, + }, + { + StringValue("abc123"), + append([]byte{byte(stringValueType), 0, 6}, []byte("abc123")...), + func(r io.Reader) (Value, error) { + var v StringValue + err := v.decode(r) + return v, err + }, + }, + { + TimestampValue( + time.Date(2014, 04, 04, 0, 1, 0, 0, time.FixedZone("PDT", -7)), + ), + append([]byte{byte(timestampValueType)}, binWrite(int64(1396569667000))...), + func(r io.Reader) (Value, error) { + var v TimestampValue + err := v.decode(r) + return v, err + }, + }, + { + UUIDValue( + [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}, + ), + []byte{byte(uuidValueType), 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}, + func(r io.Reader) (Value, error) { + var v UUIDValue + err := v.decode(r) + return v, err + }, + }, +} + +func TestValue_MarshalValue(t *testing.T) { + for i, c := range testValueEncodingCases { + var w bytes.Buffer + + if err := c.Val.encode(&w); err != nil { + t.Fatalf("%d, expect no error, got %v", i, err) + } + + if e, a := c.Expect, w.Bytes(); !reflect.DeepEqual(e, a) { + t.Errorf("%d, expect %v, got %v", i, e, a) + } + } +} + +func TestHeader_DecodeValues(t *testing.T) { + for i, c := range testValueEncodingCases { + r := bytes.NewBuffer(c.Expect) + v, err := decodeHeaderValue(r) + if err != nil { + t.Fatalf("%d, expect no error, got %v", i, err) + } + + switch tv := v.(type) { + case TimestampValue: + exp := time.Time(c.Val.(TimestampValue)) + if e, a := exp, time.Time(tv); !e.Equal(a) { + t.Errorf("%d, expect %v, got %v", i, e, a) + } + default: + if e, a := c.Val, v; !reflect.DeepEqual(e, a) { + t.Errorf("%d, expect %v, got %v", i, e, a) + } + } + } +} + +func TestValue_Decode(t *testing.T) { + for i, c := range testValueEncodingCases { + if c.Decode == nil { + continue + } + + r := bytes.NewBuffer(c.Expect) + r.ReadByte() // strip off Type field + + v, err := c.Decode(r) + if err != nil { + t.Fatalf("%d, expect no error, got %v", i, err) + } + + switch tv := v.(type) { + case TimestampValue: + exp := time.Time(c.Val.(TimestampValue)) + if e, a := exp, time.Time(tv); !e.Equal(a) { + t.Errorf("%d, expect %v, got %v", i, e, a) + } + default: + if e, a := c.Val, v; !reflect.DeepEqual(e, a) { + t.Errorf("%d, expect %v, got %v", i, e, a) + } + } + } +} + +func TestValue_String(t *testing.T) { + cases := []struct { + Val Value + Expect string + }{ + {BoolValue(true), "true"}, + {BoolValue(false), "false"}, + {Int8Value(0x0f), "0x0f"}, + {Int16Value(0x0f), "0x000f"}, + {Int32Value(0x0f), "0x0000000f"}, + {Int64Value(0x0f), "0x000000000000000f"}, + {BytesValue([]byte{0, 1, 2, 3}), "AAECAw=="}, + {StringValue("abc123"), "abc123"}, + {TimestampValue( + time.Date(2014, 04, 04, 0, 1, 0, 0, time.FixedZone("PDT", -7)), + ), + "1396569667000", + }, + {UUIDValue([16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}), + "00010203-0405-0607-0809-0a0b0c0d0e0f", + }, + } + + for i, c := range cases { + if e, a := c.Expect, c.Val.String(); e != a { + t.Errorf("%d, expect %v, got %v", i, e, a) + } + } +} diff --git a/eventstream/message.go b/eventstream/message.go new file mode 100644 index 000000000..1a77654f7 --- /dev/null +++ b/eventstream/message.go @@ -0,0 +1,99 @@ +package eventstream + +import ( + "bytes" + "encoding/binary" + "hash/crc32" +) + +const preludeLen = 8 +const preludeCRCLen = 4 +const msgCRCLen = 4 +const minMsgLen = preludeLen + preludeCRCLen + msgCRCLen + +var crc32IEEETable = crc32.MakeTable(crc32.IEEE) + +// A Message provides the eventstream message representation. +type Message struct { + Headers Headers + Payload []byte +} + +func (m *Message) rawMessage() (rawMessage, error) { + var raw rawMessage + + if len(m.Headers) > 0 { + var headers bytes.Buffer + if err := EncodeHeaders(&headers, m.Headers); err != nil { + return rawMessage{}, err + } + raw.Headers = headers.Bytes() + raw.HeadersLen = uint32(len(raw.Headers)) + } + + raw.Length = raw.HeadersLen + uint32(len(m.Payload)) + minMsgLen + + hash := crc32.New(crc32IEEETable) + binaryWriteFields(hash, binary.BigEndian, raw.Length, raw.HeadersLen) + raw.PreludeCRC = hash.Sum32() + + binaryWriteFields(hash, binary.BigEndian, raw.PreludeCRC) + + if raw.HeadersLen > 0 { + hash.Write(raw.Headers) + } + + // Read payload bytes and update hash for it as well. + if len(m.Payload) > 0 { + raw.Payload = m.Payload + hash.Write(raw.Payload) + } + + raw.CRC = hash.Sum32() + + return raw, nil +} + +// Clone returns a deep copy of the message. +func (m Message) Clone() Message { + var payload []byte + if m.Payload != nil { + payload = make([]byte, len(m.Payload)) + copy(payload, m.Payload) + } + + return Message{ + Headers: m.Headers.Clone(), + Payload: payload, + } +} + +type messagePrelude struct { + Length uint32 + HeadersLen uint32 + PreludeCRC uint32 +} + +func (p messagePrelude) PayloadLen() uint32 { + return p.Length - p.HeadersLen - minMsgLen +} + +func (p messagePrelude) ValidateLens() error { + if p.Length == 0 { + return LengthError{ + Part: "message prelude", + Want: minMsgLen, + Have: int(p.Length), + } + } + return nil +} + +type rawMessage struct { + messagePrelude + + Headers []byte + Payload []byte + + CRC uint32 +} diff --git a/eventstream/serializer.go b/eventstream/serializer.go new file mode 100644 index 000000000..018481e93 --- /dev/null +++ b/eventstream/serializer.go @@ -0,0 +1,228 @@ +package eventstream + +import ( + "math/big" + "time" + + "github.com/aws/smithy-go" + "github.com/aws/smithy-go/document" + "github.com/aws/smithy-go/traits" +) + +// ShapeSerializer wraps a [smithy.ShapeSerializer], much like the internal +// httpbinding serializer, to handle event stream message binding traits. +type ShapeSerializer struct { + Message *Message + + inner smithy.ShapeSerializer + contentType string // may be inflenced by bindings + depth int + hasBody bool +} + +var _ smithy.ShapeSerializer = (*ShapeSerializer)(nil) + +// NewShapeSerializer returns a serializer for a single Message. +func NewShapeSerializer(msg *Message, inner smithy.ShapeSerializer) *ShapeSerializer { + return &ShapeSerializer{ + Message: msg, + inner: inner, + } +} + +// ContentType returns the resolved content type for the event message payload +// after serialization, which may be affected by bindings. +func (s *ShapeSerializer) ContentType() string { + return s.contentType +} + +// Bytes returns the serialized body bytes. +func (s *ShapeSerializer) Bytes() []byte { + return s.inner.Bytes() +} + +// WriteBool implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteBool(schema *smithy.Schema, v bool) { + if isEventHeader(schema) { + s.Message.Headers.Set(schema.MemberName(), BoolValue(v)) + return + } + s.inner.WriteBool(schema, v) +} + +// WriteInt8 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt8(schema *smithy.Schema, v int8) { + if isEventHeader(schema) { + s.Message.Headers.Set(schema.MemberName(), Int8Value(v)) + return + } + s.inner.WriteInt8(schema, v) +} + +// WriteInt16 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt16(schema *smithy.Schema, v int16) { + if isEventHeader(schema) { + s.Message.Headers.Set(schema.MemberName(), Int16Value(v)) + return + } + s.inner.WriteInt16(schema, v) +} + +// WriteInt32 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt32(schema *smithy.Schema, v int32) { + if isEventHeader(schema) { + s.Message.Headers.Set(schema.MemberName(), Int32Value(v)) + return + } + s.inner.WriteInt32(schema, v) +} + +// WriteInt64 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt64(schema *smithy.Schema, v int64) { + if isEventHeader(schema) { + s.Message.Headers.Set(schema.MemberName(), Int64Value(v)) + return + } + s.inner.WriteInt64(schema, v) +} + +// WriteFloat32 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteFloat32(schema *smithy.Schema, v float32) { + s.inner.WriteFloat32(schema, v) +} + +// WriteFloat64 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteFloat64(schema *smithy.Schema, v float64) { + s.inner.WriteFloat64(schema, v) +} + +// WriteString implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteString(schema *smithy.Schema, v string) { + if isEventHeader(schema) { + s.Message.Headers.Set(schema.MemberName(), StringValue(v)) + return + } + if isEventPayload(schema) { + s.Message.Payload = []byte(v) + s.contentType = "text/plain" + return + } + s.inner.WriteString(schema, v) +} + +// WriteBlob implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteBlob(schema *smithy.Schema, v []byte) { + if isEventHeader(schema) { + s.Message.Headers.Set(schema.MemberName(), BytesValue(v)) + return + } + if isEventPayload(schema) { + s.Message.Payload = v + s.contentType = "application/octet-stream" + return + } + s.inner.WriteBlob(schema, v) +} + +// WriteTime implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteTime(schema *smithy.Schema, v time.Time) { + if isEventHeader(schema) { + s.Message.Headers.Set(schema.MemberName(), TimestampValue(v)) + return + } + s.inner.WriteTime(schema, v) +} + +// WriteBigInt implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteBigInt(schema *smithy.Schema, v *big.Int) { + s.inner.WriteBigInt(schema, v) +} + +// WriteBigFloat implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteBigFloat(schema *smithy.Schema, v *big.Float) { + s.inner.WriteBigFloat(schema, v) +} + +// WriteStruct implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteStruct(schema *smithy.Schema) { + s.depth++ + if s.depth > 1 { + s.inner.WriteStruct(schema) + return + } + // At depth 1 (the event struct itself), start a JSON body if there are + // implicit body members (members without @eventHeader or @eventPayload). + for _, m := range schema.Members() { + if !isEventBound(m) { + s.inner.WriteStruct(schema) + s.hasBody = true + return + } + } +} + +// CloseStruct implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseStruct() { + if s.depth > 1 || s.hasBody { + s.inner.CloseStruct() + } + if s.depth == 1 { + s.hasBody = false + } + s.depth-- +} + +// WriteUnion implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteUnion(schema, variant *smithy.Schema) { + s.inner.WriteUnion(schema, variant) +} + +// CloseUnion implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseUnion() { + s.inner.CloseUnion() +} + +// WriteNil implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteNil(schema *smithy.Schema) { + s.inner.WriteNil(schema) +} + +// WriteList implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteList(schema *smithy.Schema) { + s.inner.WriteList(schema) +} + +// CloseList implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseList() { + s.inner.CloseList() +} + +// WriteMap implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteMap(schema *smithy.Schema) { + s.inner.WriteMap(schema) +} + +// WriteKey implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteKey(schema *smithy.Schema, key string) { + s.inner.WriteKey(schema, key) +} + +// CloseMap implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseMap() { + s.inner.CloseMap() +} + +// WriteDocument implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteDocument(schema *smithy.Schema, v document.Value) { + s.inner.WriteDocument(schema, v) +} + +func isEventHeader(schema *smithy.Schema) bool { + _, ok := smithy.SchemaTrait[*traits.EventHeader](schema) + return ok +} + +func isEventPayload(schema *smithy.Schema) bool { + _, ok := smithy.SchemaTrait[*traits.EventPayload](schema) + return ok +} diff --git a/eventstream/shared_test.go b/eventstream/shared_test.go new file mode 100644 index 000000000..09e0f963b --- /dev/null +++ b/eventstream/shared_test.go @@ -0,0 +1,152 @@ +package eventstream + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "path/filepath" + "testing" +) + +type testCase struct { + Name string + Encoded []byte + Decoded decodedMessage +} + +type testErrorCase struct { + Name string + Encoded []byte + Err string +} + +type rawTestCase struct { + Name string + Encoded, Decoded []byte +} + +func readRawTestCases(root, class string) (map[string]rawTestCase, error) { + encoded, err := readTests(filepath.Join(root, "encoded", class)) + if err != nil { + return nil, err + } + + decoded, err := readTests(filepath.Join(root, "decoded", class)) + if err != nil { + return nil, err + } + + if len(encoded) == 0 { + return nil, fmt.Errorf("expect encoded cases, found none") + } + + if len(encoded) != len(decoded) { + return nil, fmt.Errorf("encoded and decoded sets different") + } + + rawCases := map[string]rawTestCase{} + for name, encData := range encoded { + decData, ok := decoded[name] + if !ok { + return nil, fmt.Errorf("encoded %q case not found in decoded set", name) + } + + rawCases[name] = rawTestCase{ + Name: name, + Encoded: encData, + Decoded: decData, + } + } + + return rawCases, nil +} + +func readNegativeTests(root string) ([]testErrorCase, error) { + rawCases, err := readRawTestCases(root, "negative") + if err != nil { + return nil, err + } + + cases := make([]testErrorCase, 0, len(rawCases)) + for name, rawCase := range rawCases { + cases = append(cases, testErrorCase{ + Name: name, + Encoded: rawCase.Encoded, + Err: string(rawCase.Decoded), + }) + } + + return cases, nil +} + +func readPositiveTests(root string) ([]testCase, error) { + rawCases, err := readRawTestCases(root, "positive") + if err != nil { + return nil, err + } + + cases := make([]testCase, 0, len(rawCases)) + for name, rawCase := range rawCases { + + var dec decodedMessage + if err := json.Unmarshal(rawCase.Decoded, &dec); err != nil { + return nil, fmt.Errorf("failed to decode %q, %v", name, err) + } + + cases = append(cases, testCase{ + Name: name, + Encoded: rawCase.Encoded, + Decoded: dec, + }) + } + + return cases, nil +} + +func readTests(root string) (map[string][]byte, error) { + items, err := ioutil.ReadDir(root) + if err != nil { + return nil, fmt.Errorf("failed to read test suite %q dirs, %v", root, err) + } + + cases := map[string][]byte{} + for _, item := range items { + if item.IsDir() { + continue + } + + filename := filepath.Join(root, item.Name()) + data, err := ioutil.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("failed to read test_data file %q, %v", filename, err) + } + + cases[item.Name()] = data + } + + return cases, nil +} + +func compareLines(t *testing.T, a, b []byte) bool { + as := bufio.NewScanner(bytes.NewBuffer(a)) + bs := bufio.NewScanner(bytes.NewBuffer(b)) + + var failed bool + for { + if ab, bb := as.Scan(), bs.Scan(); ab != bb { + t.Errorf("expect a & b to have same number of lines") + return false + } else if !ab { + break + } + + if v1, v2 := as.Text(), bs.Text(); v1 != v2 { + t.Errorf("expect %q to be %q", v1, v2) + failed = true + } + } + + return !failed +} diff --git a/eventstream/signer.go b/eventstream/signer.go new file mode 100644 index 000000000..69f7779d8 --- /dev/null +++ b/eventstream/signer.go @@ -0,0 +1,82 @@ +package eventstream + +import ( + "bytes" + "io" + "time" +) + +// MessageSigner signs event stream message header and payload byte pairs. +// Each invocation chains off the previous signature. +type MessageSigner interface { + SignMessage(headers, payload []byte, signingTime time.Time) ([]byte, error) +} + +// SigningWriter wraps an io.WriteCloser and signs each event stream message +// frame written to it. Each Write call MUST contain exactly one complete +// encoded event stream message frame. +// +// The signing writer wraps each incoming frame in an outer event stream +// message with :date and :chunk-signature headers, then encodes the outer +// message to the underlying writer. +// +// Close sends a signed empty message to signal end-of-stream, then closes +// the underlying writer. +type SigningWriter struct { + writer io.WriteCloser + signer MessageSigner + encoder *Encoder + + headersBuf bytes.Buffer +} + +// NewSigningWriter returns a SigningWriter that signs frames and writes them +// to w. +func NewSigningWriter(w io.WriteCloser, signer MessageSigner) *SigningWriter { + return &SigningWriter{ + writer: w, + signer: signer, + encoder: NewEncoder(), + } +} + +// Write signs a complete event stream message frame and writes the signed +// outer envelope to the underlying writer. +func (s *SigningWriter) Write(frame []byte) (int, error) { + if err := s.signAndWrite(frame); err != nil { + return 0, err + } + return len(frame), nil +} + +// Close sends a signed empty message to signal end-of-stream, then closes +// the underlying writer. +func (s *SigningWriter) Close() error { + if err := s.signAndWrite([]byte{}); err != nil { + _ = s.writer.Close() + return err + } + return s.writer.Close() +} + +func (s *SigningWriter) signAndWrite(payload []byte) error { + now := time.Now().UTC() + + var msg Message + msg.Headers.Set(DateHeader, TimestampValue(now)) + msg.Payload = payload + + s.headersBuf.Reset() + if err := EncodeHeaders(&s.headersBuf, msg.Headers); err != nil { + return err + } + + sig, err := s.signer.SignMessage(s.headersBuf.Bytes(), payload, now) + if err != nil { + return err + } + + msg.Headers.Set(ChunkSignatureHeader, BytesValue(sig)) + + return s.encoder.Encode(s.writer, msg) +} diff --git a/eventstream/testdata/decoded/negative/corrupted_header_len b/eventstream/testdata/decoded/negative/corrupted_header_len new file mode 100644 index 000000000..73e4d6f1f --- /dev/null +++ b/eventstream/testdata/decoded/negative/corrupted_header_len @@ -0,0 +1 @@ +Prelude checksum mismatch \ No newline at end of file diff --git a/eventstream/testdata/decoded/negative/corrupted_headers b/eventstream/testdata/decoded/negative/corrupted_headers new file mode 100644 index 000000000..f56e5c887 --- /dev/null +++ b/eventstream/testdata/decoded/negative/corrupted_headers @@ -0,0 +1 @@ +Message checksum mismatch \ No newline at end of file diff --git a/eventstream/testdata/decoded/negative/corrupted_length b/eventstream/testdata/decoded/negative/corrupted_length new file mode 100644 index 000000000..73e4d6f1f --- /dev/null +++ b/eventstream/testdata/decoded/negative/corrupted_length @@ -0,0 +1 @@ +Prelude checksum mismatch \ No newline at end of file diff --git a/eventstream/testdata/decoded/negative/corrupted_payload b/eventstream/testdata/decoded/negative/corrupted_payload new file mode 100644 index 000000000..f56e5c887 --- /dev/null +++ b/eventstream/testdata/decoded/negative/corrupted_payload @@ -0,0 +1 @@ +Message checksum mismatch \ No newline at end of file diff --git a/eventstream/testdata/decoded/positive/all_headers b/eventstream/testdata/decoded/positive/all_headers new file mode 100644 index 000000000..fd8f96b88 --- /dev/null +++ b/eventstream/testdata/decoded/positive/all_headers @@ -0,0 +1,58 @@ +{ + "total_length": 204, + "headers_length": 175, + "prelude_crc": 263087306, + "headers": [ { + "name": "event-type", + "type": 4, + "value": 40972 + }, + { + "name": "content-type", + "type": 7, + "value": "YXBwbGljYXRpb24vanNvbg==" + }, + { + "name": "bool false", + "type": 1, + "value": false + }, + { + "name": "bool true", + "type": 0, + "value": true + }, + { + "name": "byte", + "type": 2, + "value": -49 + }, + { + "name": "byte buf", + "type": 6, + "value": "SSdtIGEgbGl0dGxlIHRlYXBvdCE=" + }, + { + "name": "timestamp", + "type": 8, + "value": 8675309 + }, + { + "name": "int16", + "type": 3, + "value": 42 + }, + { + "name": "int64", + "type": 5, + "value": 42424242 + }, + { + "name": "uuid", + "type": 9, + "value": "AQIDBAUGBwgJCgsMDQ4PEA==" + } + ], + "payload": "eydmb28nOidiYXInfQ==", + "message_crc": -1415188212 +} diff --git a/eventstream/testdata/decoded/positive/empty_message b/eventstream/testdata/decoded/positive/empty_message new file mode 100644 index 000000000..1d35df8e6 --- /dev/null +++ b/eventstream/testdata/decoded/positive/empty_message @@ -0,0 +1,8 @@ +{ + "total_length": 16, + "headers_length": 0, + "prelude_crc": 96618731, + "headers": [ ], + "payload": "", + "message_crc": 2107164927 +} diff --git a/eventstream/testdata/decoded/positive/int32_header b/eventstream/testdata/decoded/positive/int32_header new file mode 100644 index 000000000..852a0db6e --- /dev/null +++ b/eventstream/testdata/decoded/positive/int32_header @@ -0,0 +1,13 @@ +{ + "total_length": 45, + "headers_length": 16, + "prelude_crc": 1103373496, + "headers": [ { + "name": "event-type", + "type": 4, + "value": 40972 + } + ], + "payload": "eydmb28nOidiYXInfQ==", + "message_crc": 921993376 +} diff --git a/eventstream/testdata/decoded/positive/payload_no_headers b/eventstream/testdata/decoded/positive/payload_no_headers new file mode 100644 index 000000000..1c96631ca --- /dev/null +++ b/eventstream/testdata/decoded/positive/payload_no_headers @@ -0,0 +1,8 @@ +{ + "total_length": 29, + "headers_length": 0, + "prelude_crc": -44921766, + "headers": [ ], + "payload": "eydmb28nOidiYXInfQ==", + "message_crc": -1016776394 +} diff --git a/eventstream/testdata/decoded/positive/payload_one_str_header b/eventstream/testdata/decoded/positive/payload_one_str_header new file mode 100644 index 000000000..e3bfd312f --- /dev/null +++ b/eventstream/testdata/decoded/positive/payload_one_str_header @@ -0,0 +1,13 @@ +{ + "total_length": 61, + "headers_length": 32, + "prelude_crc": 134054806, + "headers": [ { + "name": "content-type", + "type": 7, + "value": "YXBwbGljYXRpb24vanNvbg==" + } + ], + "payload": "eydmb28nOidiYXInfQ==", + "message_crc": -1919153999 +} diff --git a/eventstream/testdata/encoded/negative/corrupted_header_len b/eventstream/testdata/encoded/negative/corrupted_header_len new file mode 100644 index 000000000..474929c83 Binary files /dev/null and b/eventstream/testdata/encoded/negative/corrupted_header_len differ diff --git a/eventstream/testdata/encoded/negative/corrupted_headers b/eventstream/testdata/encoded/negative/corrupted_headers new file mode 100644 index 000000000..802a2276c Binary files /dev/null and b/eventstream/testdata/encoded/negative/corrupted_headers differ diff --git a/eventstream/testdata/encoded/negative/corrupted_length b/eventstream/testdata/encoded/negative/corrupted_length new file mode 100644 index 000000000..4e55a3196 Binary files /dev/null and b/eventstream/testdata/encoded/negative/corrupted_length differ diff --git a/eventstream/testdata/encoded/negative/corrupted_payload b/eventstream/testdata/encoded/negative/corrupted_payload new file mode 100644 index 000000000..2ee9ef3ce Binary files /dev/null and b/eventstream/testdata/encoded/negative/corrupted_payload differ diff --git a/eventstream/testdata/encoded/positive/all_headers b/eventstream/testdata/encoded/positive/all_headers new file mode 100644 index 000000000..b986af148 Binary files /dev/null and b/eventstream/testdata/encoded/positive/all_headers differ diff --git a/eventstream/testdata/encoded/positive/empty_message b/eventstream/testdata/encoded/positive/empty_message new file mode 100644 index 000000000..663632857 Binary files /dev/null and b/eventstream/testdata/encoded/positive/empty_message differ diff --git a/eventstream/testdata/encoded/positive/int32_header b/eventstream/testdata/encoded/positive/int32_header new file mode 100644 index 000000000..4e13b503d Binary files /dev/null and b/eventstream/testdata/encoded/positive/int32_header differ diff --git a/eventstream/testdata/encoded/positive/payload_no_headers b/eventstream/testdata/encoded/positive/payload_no_headers new file mode 100644 index 000000000..47733a111 Binary files /dev/null and b/eventstream/testdata/encoded/positive/payload_no_headers differ diff --git a/eventstream/testdata/encoded/positive/payload_one_str_header b/eventstream/testdata/encoded/positive/payload_one_str_header new file mode 100644 index 000000000..d4abaa7f8 Binary files /dev/null and b/eventstream/testdata/encoded/positive/payload_one_str_header differ diff --git a/eventstream/types.go b/eventstream/types.go new file mode 100644 index 000000000..4627bb209 --- /dev/null +++ b/eventstream/types.go @@ -0,0 +1,26 @@ +package eventstream + +import "github.com/aws/smithy-go" + +// UnknownUnionMember is returned when a union member is returned over the +// wire, but has an unknown tag. +type UnknownUnionMember struct { + Tag string + Value []byte +} + +// Deserialize is a no-op. The raw bytes are already captured in Value. +func (*UnknownUnionMember) Deserialize(smithy.ShapeDeserializer) error { + return nil +} + +// UnknownMessageError provides an error when a message is received from the +// stream, but the reader is unable to determine what kind of message it is. +type UnknownMessageError struct { + Type string + Message *Message +} + +func (e *UnknownMessageError) Error() string { + return "unknown event stream message type, " + e.Type +} diff --git a/internal/errors/errors.go b/internal/errors/errors.go new file mode 100644 index 000000000..5eea7b798 --- /dev/null +++ b/internal/errors/errors.go @@ -0,0 +1,56 @@ +package errors + +import ( + "reflect" + "strings" + + "github.com/aws/smithy-go" +) + +// SetErrorCodeOverride sets the ErrorCodeOverride field on err if present. +// +// This is used by protocols in awsquery-compatible mode that need to override +// the error code on a deserialized error. +func SetErrorCodeOverride(err error, code string) { + // yes it's reflection but i don't really view errors as a hot path so i + // think it's fine + v := reflect.ValueOf(err) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + if v.Kind() != reflect.Struct { + return + } + + f := v.FieldByName("ErrorCodeOverride") + if !f.IsValid() || !f.CanSet() || f.Type() != reflect.TypeOf((*string)(nil)) { + return + } + + f.Set(reflect.ValueOf(&code)) +} + +// SanitizeErrorCode strips namespace prefixes and colon suffixes from protocol +// error codes. +func SanitizeErrorCode(code string) string { + _, noprefix, ok := strings.Cut(code, "#") + if !ok { + noprefix = code // If sep does not appear in s, cut returns s, "", false. + } + + code, _, _ = strings.Cut(noprefix, ":") + return code +} + +// ParseQueryError extracts the error code and fault from X-Amzn-Query-Error. +func ParseQueryError(header string) (string, smithy.ErrorFault) { + code, fault, _ := strings.Cut(header, ";") + switch fault { + case "Sender": + return code, smithy.FaultClient + case "Receiver": + return code, smithy.FaultServer + default: + return code, smithy.FaultUnknown + } +} diff --git a/internal/eventstream/codec.go b/internal/eventstream/codec.go new file mode 100644 index 000000000..f18e15782 --- /dev/null +++ b/internal/eventstream/codec.go @@ -0,0 +1,222 @@ +package eventstream + +import ( + "bytes" + "fmt" + "io" + + "github.com/aws/smithy-go" + "github.com/aws/smithy-go/eventstream" +) + +// Codec orchestrates event stream message serde for protocols that use the +// standard event stream binary framing. +type Codec struct { + Serializer func() smithy.ShapeSerializer + Deserializer func([]byte) smithy.ShapeDeserializer + ContentType string + + encoder *eventstream.Encoder + decoder *eventstream.Decoder + payloadBuf []byte +} + +func (c *Codec) enc() *eventstream.Encoder { + if c.encoder == nil { + c.encoder = eventstream.NewEncoder() + } + return c.encoder +} + +func (c *Codec) dec() *eventstream.Decoder { + if c.decoder == nil { + c.decoder = eventstream.NewDecoder() + } + return c.decoder +} + +// SerializeEventMessage serializes an event to the input stream. +func (c *Codec) SerializeEventMessage(schema, variant *smithy.Schema, v smithy.Serializable, w io.Writer) error { + var msg eventstream.Message + + inner := c.Serializer() + ss := eventstream.NewShapeSerializer(&msg, inner) + + v.Serialize(ss) + + msg.Headers.Set(eventstream.MessageTypeHeader, eventstream.StringValue(eventstream.EventMessageType)) + msg.Headers.Set(eventstream.EventTypeHeader, eventstream.StringValue(variant.MemberName())) + + if ct := ss.ContentType(); ct != "" { + msg.Headers.Set(eventstream.ContentTypeHeader, eventstream.StringValue(ct)) + } else if payload := inner.Bytes(); len(payload) > 0 { + msg.Payload = payload + msg.Headers.Set(eventstream.ContentTypeHeader, eventstream.StringValue(c.ContentType)) + } + + return c.enc().Encode(w, msg) +} + +// DeserializeEventMessage reads an event from the output stream. +func (c *Codec) DeserializeEventMessage(schema *smithy.Schema, types *smithy.TypeRegistry, r io.Reader) (smithy.Deserializable, error) { + for { + c.payloadBuf = c.payloadBuf[0:0] + msg, err := c.dec().Decode(r, c.payloadBuf) + if err != nil { + if isEOF(err) { + return nil, io.EOF + } + return nil, fmt.Errorf("decode event: %w", err) + } + + msgType := msg.Headers.Get(eventstream.MessageTypeHeader) + if msgType == nil { + return nil, fmt.Errorf("missing %s header", eventstream.MessageTypeHeader) + } + + switch msgType.String() { + case eventstream.EventMessageType: + event, err := c.deserializeEvent(schema, types, &msg) + if err != nil { + return nil, err + } + return event, nil + case eventstream.ExceptionMessageType: + return nil, c.deserializeException(schema, types, &msg) + case eventstream.ErrorMessageType: + return nil, deserializeError(&msg) + default: + mc := msg.Clone() + return nil, &eventstream.UnknownMessageError{ + Type: msgType.String(), + Message: &mc, + } + } + } +} + +func (c *Codec) deserializeEvent(schema *smithy.Schema, types *smithy.TypeRegistry, msg *eventstream.Message) (smithy.Deserializable, error) { + eventType := msg.Headers.Get(eventstream.EventTypeHeader) + if eventType == nil { + return nil, fmt.Errorf("missing %s header", eventstream.EventTypeHeader) + } + + member := schema.Member(eventType.String()) + if member == nil { + return c.unknownEvent(eventType.String(), msg) + } + + entry, ok := types.LookupEntry(member.TargetID().String()) + if !ok { + return c.unknownEvent(eventType.String(), msg) + } + + instance, ok := entry.New().(smithy.Deserializable) + if !ok { + return nil, fmt.Errorf("event type %s is not deserializable", eventType.String()) + } + + inner := c.Deserializer(msg.Payload) + ed := eventstream.NewShapeDeserializer(msg, inner) + if err := instance.Deserialize(ed); err != nil { + return nil, fmt.Errorf("deserialize event %s: %w", eventType.String(), err) + } + + return instance, nil +} + +func (c *Codec) unknownEvent(tag string, msg *eventstream.Message) (*eventstream.UnknownUnionMember, error) { + var buf bytes.Buffer + c.enc().Encode(&buf, *msg) + return &eventstream.UnknownUnionMember{Tag: tag, Value: buf.Bytes()}, nil +} + +func (c *Codec) deserializeException(schema *smithy.Schema, types *smithy.TypeRegistry, msg *eventstream.Message) error { + exType := msg.Headers.Get(eventstream.ExceptionTypeHeader) + if exType == nil { + return fmt.Errorf("missing %s header", eventstream.ExceptionTypeHeader) + } + + var id string + if member := schema.Member(exType.String()); member != nil { + id = member.TargetID().String() + } else { + id = exType.String() + } + + perr, ok := types.DeserializableError(id) + if !ok { + return &smithy.GenericAPIError{ + Code: exType.String(), + Message: "unknown exception", + } + } + + inner := c.Deserializer(msg.Payload) + ed := eventstream.NewShapeDeserializer(msg, inner) + if err := perr.Deserialize(ed); err != nil { + return fmt.Errorf("deserialize exception %s: %w", exType.String(), err) + } + + return perr +} + +// SerializeInitialRequest serializes the operation input as the first event +// stream message with :event-type "initial-request". +func (c *Codec) SerializeInitialRequest(schema *smithy.Schema, v smithy.Serializable, w io.Writer) error { + ss := c.Serializer() + v.Serialize(ss) + + var msg eventstream.Message + msg.Headers.Set(eventstream.MessageTypeHeader, eventstream.StringValue(eventstream.EventMessageType)) + msg.Headers.Set(eventstream.EventTypeHeader, eventstream.StringValue("initial-request")) + if payload := ss.Bytes(); len(payload) > 0 { + msg.Payload = payload + msg.Headers.Set(eventstream.ContentTypeHeader, eventstream.StringValue(c.ContentType)) + } + + return c.enc().Encode(w, msg) +} + +// DeserializeInitialResponse reads the first event stream message and +// deserializes it as the operation output. +func (c *Codec) DeserializeInitialResponse(schema *smithy.Schema, r io.Reader, out smithy.Deserializable) error { + c.payloadBuf = c.payloadBuf[0:0] + msg, err := c.dec().Decode(r, c.payloadBuf) + if err != nil { + return fmt.Errorf("decode initial response: %w", err) + } + + eventType := msg.Headers.Get(eventstream.EventTypeHeader) + if eventType == nil || eventType.String() != "initial-response" { + return fmt.Errorf("expected initial-response, got %v", eventType) + } + + if len(msg.Payload) > 0 { + sd := c.Deserializer(msg.Payload) + if err := out.Deserialize(sd); err != nil { + return fmt.Errorf("deserialize initial response: %w", err) + } + } + + return nil +} + +func deserializeError(msg *eventstream.Message) error { + code := "UnknownError" + message := code + if v := msg.Headers.Get(eventstream.ErrorCodeHeader); v != nil { + code = v.String() + } + if v := msg.Headers.Get(eventstream.ErrorMessageHeader); v != nil { + message = v.String() + } + return &smithy.GenericAPIError{ + Code: code, + Message: message, + } +} + +func isEOF(err error) bool { + return err == io.EOF || err == io.ErrUnexpectedEOF +} diff --git a/internal/eventstream/no_event_stream.go b/internal/eventstream/no_event_stream.go new file mode 100644 index 000000000..3862cdb53 --- /dev/null +++ b/internal/eventstream/no_event_stream.go @@ -0,0 +1,36 @@ +package eventstream + +import ( + "fmt" + "io" + + "github.com/aws/smithy-go" +) + +// NoEventStream implements the event stream methods of +// [github.com/aws/smithy-go/transport/http.ClientProtocol] for protocols +// that do not support event streams. +type NoEventStream struct{} + +// HasInitialEventMessage returns false. +func (NoEventStream) HasInitialEventMessage() bool { return false } + +// SerializeEventMessage returns an error. +func (NoEventStream) SerializeEventMessage(_, _ *smithy.Schema, _ smithy.Serializable, _ io.Writer) error { + return fmt.Errorf("event streams are not supported by this protocol") +} + +// DeserializeEventMessage returns an error. +func (NoEventStream) DeserializeEventMessage(_ *smithy.Schema, _ *smithy.TypeRegistry, _ io.Reader) (smithy.Deserializable, error) { + return nil, fmt.Errorf("event streams are not supported by this protocol") +} + +// SerializeInitialRequest returns an error. +func (NoEventStream) SerializeInitialRequest(_ *smithy.Schema, _ smithy.Serializable, _ io.Writer) error { + return fmt.Errorf("event streams are not supported by this protocol") +} + +// DeserializeInitialResponse returns an error. +func (NoEventStream) DeserializeInitialResponse(_ *smithy.Schema, _ io.Reader, _ smithy.Deserializable) error { + return fmt.Errorf("event streams are not supported by this protocol") +} diff --git a/internal/eventstream/require_http2.go b/internal/eventstream/require_http2.go new file mode 100644 index 000000000..78910b7d5 --- /dev/null +++ b/internal/eventstream/require_http2.go @@ -0,0 +1,12 @@ +package eventstream + +import "fmt" + +// RequireBidiHTTP2 returns an error if the response protocol is not at least +// HTTP/2, which is required for bidirectional event streams. +func (c *Codec) RequireBidiHTTP2(proto string, protoMajor int) error { + if protoMajor < 2 { + return fmt.Errorf("operation requires minimum HTTP protocol of HTTP/2, but was %s", proto) + } + return nil +} diff --git a/internal/serde/stack.go b/internal/serde/stack.go new file mode 100644 index 000000000..dd236e3ab --- /dev/null +++ b/internal/serde/stack.go @@ -0,0 +1,53 @@ +package serde + +// Stack is a generic slice-backed stack pre-allocated to avoid growth +// allocations for typical serde nesting depths. +type Stack[T any] struct { + values []T +} + +// NewStack returns a Stack pre-allocated with capacity 8. +func NewStack[T any]() Stack[T] { + return Stack[T]{values: make([]T, 0, 8)} +} + +// Push adds a value to the top of the stack. +func (s *Stack[T]) Push(v T) { + s.values = append(s.values, v) +} + +// Pop removes and returns the top value. +func (s *Stack[T]) Pop() T { + v := s.values[len(s.values)-1] + var zero T + s.values[len(s.values)-1] = zero + s.values = s.values[:len(s.values)-1] + return v +} + +// Top returns a pointer to the top value without removing it. +func (s *Stack[T]) Top() *T { + if len(s.values) == 0 { + return nil + } + return &s.values[len(s.values)-1] +} + +// Len returns the number of elements in the stack. +func (s *Stack[T]) Len() int { + return len(s.values) +} + +// Reset clears the stack while retaining allocated capacity. +func (s *Stack[T]) Reset() { + var zero T + for i := range s.values { + s.values[i] = zero + } + s.values = s.values[:0] +} + +// Values returns the underlying slice for indexed access. +func (s *Stack[T]) Values() []T { + return s.values +} diff --git a/prelude/prelude.go b/prelude/prelude.go new file mode 100644 index 000000000..8d3333a75 --- /dev/null +++ b/prelude/prelude.go @@ -0,0 +1,31 @@ +// Package prelude defines schemas for the Smithy prelude shapes. +package prelude + +import "github.com/aws/smithy-go" + +// All shapes in the prelude. +var ( + String = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "String"}, smithy.ShapeTypeString, 0) + Blob = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "Blob"}, smithy.ShapeTypeBlob, 0) + Boolean = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "Boolean"}, smithy.ShapeTypeBoolean, 0) + Byte = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "Byte"}, smithy.ShapeTypeByte, 0) + Short = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "Short"}, smithy.ShapeTypeShort, 0) + Integer = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "Integer"}, smithy.ShapeTypeInteger, 0) + Long = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "Long"}, smithy.ShapeTypeLong, 0) + Float = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "Float"}, smithy.ShapeTypeFloat, 0) + Double = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "Double"}, smithy.ShapeTypeDouble, 0) + Timestamp = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "Timestamp"}, smithy.ShapeTypeTimestamp, 0) + Document = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "Document"}, smithy.ShapeTypeDocument, 0) + Unit = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "Unit"}, smithy.ShapeTypeStructure, 0) + + PrimitiveBoolean = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "PrimitiveBoolean"}, smithy.ShapeTypeBoolean, 0) + PrimitiveByte = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "PrimitiveByte"}, smithy.ShapeTypeByte, 0) + PrimitiveShort = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "PrimitiveShort"}, smithy.ShapeTypeShort, 0) + PrimitiveInteger = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "PrimitiveInteger"}, smithy.ShapeTypeInteger, 0) + PrimitiveLong = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "PrimitiveLong"}, smithy.ShapeTypeLong, 0) + PrimitiveFloat = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "PrimitiveFloat"}, smithy.ShapeTypeFloat, 0) + PrimitiveDouble = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "PrimitiveDouble"}, smithy.ShapeTypeDouble, 0) + + BigInteger = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "BigInteger"}, smithy.ShapeTypeBigInteger, 0) + BigDecimal = smithy.NewSchema(smithy.ShapeID{Namespace: "smithy.api", Name: "BigDecimal"}, smithy.ShapeTypeBigDecimal, 0) +) diff --git a/schema.go b/schema.go new file mode 100644 index 000000000..6293d34b1 --- /dev/null +++ b/schema.go @@ -0,0 +1,328 @@ +package smithy + +import ( + "fmt" + "strings" + "sync/atomic" + "unsafe" +) + +// ShapeType is a type of Smithy shape. +// See https://smithy.io/2.0/spec/idl.html#defining-shapes. +type ShapeType int + +// Enumerates ShapeType per the Smithy IDL. +const ( + ShapeTypeBlob ShapeType = iota + ShapeTypeBoolean + ShapeTypeString + ShapeTypeTimestamp + ShapeTypeByte + ShapeTypeShort + ShapeTypeInteger + ShapeTypeLong + ShapeTypeFloat + ShapeTypeDocument + ShapeTypeDouble + ShapeTypeBigDecimal + ShapeTypeBigInteger + ShapeTypeEnum + ShapeTypeIntEnum + ShapeTypeList + ShapeTypeSet + ShapeTypeMap + ShapeTypeStructure + ShapeTypeUnion + ShapeTypeMember + ShapeTypeService + ShapeTypeResource + ShapeTypeOperation +) + +// ShapeID fields of a Smithy shape ID. +type ShapeID struct { + Namespace, Name, Member string +} + +// String returns the IDL microformat for the shape ID. +func (s ShapeID) String() string { + if s.Member == "" { + return fmt.Sprintf("%s#%s", s.Namespace, s.Name) + } + return fmt.Sprintf("%s#%s$%s", s.Namespace, s.Name, s.Member) +} + +func stoid(s string) ShapeID { + ns, n, _ := strings.Cut(s, "#") + n, m, _ := strings.Cut(n, "$") + return ShapeID{ns, n, m} +} + +// Schema encodes information about a shape from a Smithy model. +// +// Generated clients use schemas at runtime to dynamically (de)serialize +// request/responses. +type Schema struct { + id ShapeID + typ ShapeType + members map[string]*Schema // member name -> schema + traits map[ShapeID]Trait // trait ID -> non-indexed traits only + indexed []Trait // indexed trait slots, sized to max index present + directMask uint64 // bitmask: bit i set means indexed[i] was declared directly on this schema + targetID ShapeID // for member schemas, the target's shape ID + + listMember *Schema + mapKey, mapValue *Schema + + ext [numExtensionSlots]unsafe.Pointer // lazily-computed codec extensions, accessed atomically +} + +// NewSchema creates a new Schema with the given shape ID and traits. +func NewSchema(id ShapeID, typ ShapeType, numMembers int, ts ...Trait) *Schema { + s := &Schema{ + id: id, + typ: typ, + members: make(map[string]*Schema, numMembers), + } + for _, t := range ts { + s.addTrait(t, true) + } + return s +} + +func (s *Schema) addTrait(t Trait, direct bool) { + if it, ok := t.(IndexableTrait); ok { + idx := it.TraitIndex() + if idx >= len(s.indexed) { + s.indexed = append(s.indexed, make([]Trait, idx-len(s.indexed)+1)...) + } + s.indexed[idx] = t + if direct { + s.directMask |= 1 << uint(idx) + } + return + } + + if s.traits == nil { + s.traits = map[ShapeID]Trait{} + } + s.traits[t.TraitID()] = t +} + +// AddMember adds a member to the schema derived from the target, with +// optional trait overrides. The member schema is returned for caller +// reference. +// +// The member schema's effective trait view (accessed via [SchemaTrait]) +// inherits all of the target's traits, then applies the overrides. The +// member's direct trait view (accessed via [SchemaDirectTrait]) contains +// only the overrides, i.e. the traits declared directly on the member. +func (s *Schema) AddMember(name string, target *Schema, ts ...Trait) *Schema { + m := &Schema{ + id: ShapeID{Member: name}, + typ: target.typ, + members: target.members, + indexed: cloneIndexed(target.indexed), + traits: cloneTraits(target.traits), + directMask: 0, // inherited traits are not direct + targetID: target.id, + listMember: target.listMember, + mapKey: target.mapKey, + mapValue: target.mapValue, + } + + // member-declared traits override and are direct + for _, t := range ts { + m.addTrait(t, true) + } + + s.members[name] = m + + // Invalidate cached extensions, schema structure changed. + for i := range s.ext { + atomic.StorePointer(&s.ext[i], nil) + } + + switch name { + case "member": + s.listMember = m + case "key": + s.mapKey = m + case "value": + s.mapValue = m + } + return m +} + +func cloneIndexed(src []Trait) []Trait { + if src == nil { + return nil + } + dst := make([]Trait, len(src)) + copy(dst, src) + return dst +} + +func cloneTraits(src map[ShapeID]Trait) map[ShapeID]Trait { + if src == nil { + return nil + } + dst := make(map[ShapeID]Trait, len(src)) + for k, v := range src { + dst[k] = v + } + return dst +} + +// ListMember returns the "member" schema for list types. +func (s *Schema) ListMember() *Schema { + return s.listMember +} + +// MapKey returns the "key" schema for map types. +func (s *Schema) MapKey() *Schema { + return s.mapKey +} + +// MapValue returns the "value" schema for map types. +func (s *Schema) MapValue() *Schema { + return s.mapValue +} + +// MemberName returns the member component of the schema's shape ID. +func (s *Schema) MemberName() string { + return s.id.Member +} + +// ID returns the shape ID of the schema. +func (s *Schema) ID() ShapeID { + return s.id +} + +// TargetID returns the shape ID of the member's target shape. +func (s *Schema) TargetID() ShapeID { + return s.targetID +} + +// Type returns the shape type of the schema. +func (s *Schema) Type() ShapeType { + return s.typ +} + +// Member returns the member schema for the given name, or nil. +func (s *Schema) Member(name string) *Schema { + return s.members[name] +} + +// Members returns the schema's members as a map of name to schema. +func (s *Schema) Members() map[string]*Schema { + return s.members +} + +// OperationSchema describes an operation, which is essentially its own schema +// with additional pointers to its input and output. +type OperationSchema struct { + *Schema + Input, Output *Schema + + inputStream, outputStream bool +} + +// NewOperationSchema returns an OperationSchema for (input, output). +func NewOperationSchema(op, input, output *Schema) *OperationSchema { + return &OperationSchema{ + Schema: op, + Input: input, + Output: output, + inputStream: isEventStream(input), + outputStream: isEventStream(output), + } +} + +// IsInputEventStream reports whether this is an input event stream. +func (s *OperationSchema) IsInputEventStream() bool { + return s.inputStream +} + +// IsOutputEventStream reports whether this is an output event stream. +func (s *OperationSchema) IsOutputEventStream() bool { + return s.outputStream +} + +// ServiceSchema describes a service shape. +type ServiceSchema struct { + *Schema + Version string +} + +// NewServiceSchema returns a ServiceSchema for the given service shape. +func NewServiceSchema(schema *Schema, version string) *ServiceSchema { + return &ServiceSchema{Schema: schema, Version: version} +} + +// SchemaTrait returns the target trait on the schema if it exists. +// +// For member schemas this returns the effective trait, which is the trait +// declared directly on the member if present, else the trait inherited from +// the target shape. +func SchemaTrait[T Trait](s *Schema) (T, bool) { + return schemaTrait[T](s, false) +} + +// SchemaDirectTrait returns the target trait on the schema if it was +// declared directly on the schema. +// +// For member schemas this returns the trait only if it was declared on the +// member itself, ignoring any trait inherited from the target shape. For +// non-member schemas this is equivalent to [SchemaTrait]. +func SchemaDirectTrait[T Trait](s *Schema) (T, bool) { + return schemaTrait[T](s, true) +} + +func schemaTrait[T Trait](s *Schema, directOnly bool) (T, bool) { + var zero T + + if s == nil { + return zero, false + } + + if it, ok := Trait(zero).(IndexableTrait); ok { + idx := it.TraitIndex() + if idx >= len(s.indexed) { + return zero, false + } + if directOnly && s.directMask&(1< indexStreaming && m.indexed[indexStreaming] != nil { + return true + } + } + return false +} diff --git a/schema_ext.go b/schema_ext.go new file mode 100644 index 000000000..7503b30b8 --- /dev/null +++ b/schema_ext.go @@ -0,0 +1,37 @@ +package smithy + +import ( + "sync/atomic" + "unsafe" +) + +// ExtensionID identifies a schema extension slot. Each codec family +// (JSON, CBOR, etc.) uses a distinct slot to cache precomputed data. +type ExtensionID int + +const numExtensionSlots = 4 + +const ( + ExtJSON ExtensionID = iota // transport/http/protocol/internal/json + ExtCBOR // transport/http/protocol/internal/cbor + ExtXML // transport/http/protocol/internal/xml + ExtQuery // transport/http/protocol/internal/query +) + +// SchemaExtension retrieves or lazily computes the extension for the given +// slot. build is called on first access for a schema and the result is cached. +// The build function must return a pointer to an immutable value. +func SchemaExtension[T any](s *Schema, id ExtensionID, build func(*Schema) *T) *T { + p := atomic.LoadPointer(&s.ext[id]) + if p != nil { + return (*T)(p) + } + return computeSchemaExtension(s, id, build) +} + +//go:noinline +func computeSchemaExtension[T any](s *Schema, id ExtensionID, build func(*Schema) *T) *T { + v := build(s) + atomic.StorePointer(&s.ext[id], unsafe.Pointer(v)) + return v +} diff --git a/serde.go b/serde.go new file mode 100644 index 000000000..dacc4390e --- /dev/null +++ b/serde.go @@ -0,0 +1,231 @@ +package smithy + +import ( + "fmt" + "io" + "math/big" + "time" + + "github.com/aws/smithy-go/document" +) + +// ShapeSerializer implements the marshaling of an in-code representation of a +// shape to an unspecified data format, which is determined by the +// implementation. +// +// A ShapeSerializer is consumed by the **code-generated** Serialize() method +// of a modeled structure. For example: +// +// func (v *PutItemInput) Serialize(s smithy.ShapeSerializer) { +// s.WriteStruct(schemas.PutItemInput) +// v.SerializeMembers(s) +// s.CloseStruct() +// } +// +// func (v *PutItemInput) SerializeMembers(s smithy.ShapeSerializer) { +// if v.TableName != nil { +// s.WriteString(schemas.PutItemInput_TableName, *v.TableName) +// } +// if v.Item != nil { +// serializeAttributeMap(s, schemas.PutItemInput_Item, v.Item) +// } +// // ... +// } +type ShapeSerializer interface { + Bytes() []byte + + WriteInt8(*Schema, int8) + WriteInt16(*Schema, int16) + WriteInt32(*Schema, int32) + WriteInt64(*Schema, int64) + WriteFloat32(*Schema, float32) + WriteFloat64(*Schema, float64) + WriteBool(*Schema, bool) + WriteString(*Schema, string) + WriteBigInt(*Schema, *big.Int) + WriteBigFloat(*Schema, *big.Float) + WriteBlob(*Schema, []byte) + WriteTime(*Schema, time.Time) + + WriteUnion(schema, variant *Schema) + CloseUnion() + WriteDocument(*Schema, document.Value) + WriteNil(*Schema) + + WriteStruct(*Schema) + CloseStruct() + + WriteList(*Schema) + CloseList() + + WriteMap(*Schema) + WriteKey(*Schema, string) + CloseMap() +} + +// ShapeDeserializer implements the unmarshaling from some unspecified data +// format to an in-code representation of a shape, which is determined by the +// implementation. +type ShapeDeserializer interface { + ReadInt8(*Schema, *int8) error + ReadInt16(*Schema, *int16) error + ReadInt32(*Schema, *int32) error + ReadInt64(*Schema, *int64) error + ReadFloat32(*Schema, *float32) error + ReadFloat64(*Schema, *float64) error + ReadBool(*Schema, *bool) error + ReadString(*Schema, *string) error + ReadBlob(*Schema, *[]byte) error + ReadTime(*Schema, *time.Time) error + ReadBigInt(*Schema, *big.Int) error + ReadBigFloat(*Schema, *big.Float) error + ReadNil(*Schema) (bool, error) + + ReadStruct(*Schema) error + ReadStructMember() (*Schema, error) + + ReadUnion(*Schema) (*Schema, error) + ReadDocument(*Schema, *document.Value) error + + ReadList(*Schema) error + ReadListItem(*Schema) (hasMoreElements bool, err error) + + ReadMap(*Schema) error + ReadMapKey(*Schema) (key string, hasMoreElements bool, err error) +} + +// Serializable is an entity that can describe itself to a ShapeSerializer to +// be encoded to some format. +// +// Unlike the standard library marshaler interfaces, which idiomatically encode +// to []byte, the output format and data type here is not specified at all. +// This is because Smithy shapes need to encode to a variety of formats or data +// carriers. For example, HTTP-binding JSON protocols need to serialize some +// members to bytes (the HTTP request body) and others directly to fields on +// the HTTP request itself (e.g. headers). +type Serializable interface { + Serialize(ShapeSerializer) +} + +// StreamingInput is implemented by input types that have a streaming blob +// payload (an io.Reader member with @httpPayload + @streaming). +type StreamingInput interface { + GetPayloadStream() io.Reader +} + +// StreamingOutput is implemented by output types that have a streaming blob +// payload (an io.ReadCloser member with @httpPayload + @streaming). +type StreamingOutput interface { + SetPayloadStream(io.ReadCloser) +} + +// Deserializable is an entity that can unmarshal itself from a +// ShapeDeserializer. +type Deserializable interface { + Deserialize(ShapeDeserializer) error +} + +// DeserializableError is implemented by modeled error types for a service. +type DeserializableError interface { + Deserializable + error +} + +// ReadUnion is a utility API for generated clients. +func ReadUnion(d ShapeDeserializer, schema *Schema, memberFn func(*Schema) error) error { + ms, err := d.ReadUnion(schema) + if err != nil { + return err + } + + if ms != nil { + if err := memberFn(ms); err != nil { + return err + } + } + + for { + ms, err = d.ReadUnion(schema) + if err != nil { + return err + } + if ms == nil { + return nil + } + return fmt.Errorf("union has more than one non-nil member: %s", ms.MemberName()) + } +} + +// ReadStruct is a utility API for generated clients. +func ReadStruct(d ShapeDeserializer, schema *Schema, memberFn func(*Schema) error) error { + if err := d.ReadStruct(schema); err != nil { + return err + } + + for { + ms, err := d.ReadStructMember() + if err != nil { + return err + } + + if ms == nil { + return nil + } + + if err := memberFn(ms); err != nil { + return err + } + } +} + +// ReadList is a utility API for generated clients. +func ReadList(d ShapeDeserializer, schema *Schema, memberFn func() error) error { + if err := d.ReadList(schema); err != nil { + return err + } + + var memberSchema *Schema + if schema != nil { + memberSchema = schema.ListMember() + } + + for { + ok, err := d.ReadListItem(memberSchema) + if !ok { + return nil + } + if err != nil { + return err + } + + if err := memberFn(); err != nil { + return err + } + } +} + +// ReadMap is a utility API for generated clients. +func ReadMap(d ShapeDeserializer, schema *Schema, memberFn func(string) error) error { + if err := d.ReadMap(schema); err != nil { + return err + } + + var keySchema *Schema + if schema != nil { + keySchema = schema.MapKey() + } + + for { + k, ok, err := d.ReadMapKey(keySchema) + if !ok { + return nil + } + if err != nil { + return err + } + + if err := memberFn(k); err != nil { + return err + } + } +} diff --git a/testing/xml/xmlToStruct.go b/testing/xml/xmlToStruct.go index 1bfc7ba98..7ea21ecf9 100644 --- a/testing/xml/xmlToStruct.go +++ b/testing/xml/xmlToStruct.go @@ -137,7 +137,7 @@ func StructToXML(e *xml.Encoder, node *XMLNode, sorted bool) error { // Sort Attributes attrs := node.Attr if sorted { - sortedAttrs := make([]xml.Attr, len(attrs)) + sortedAttrs := make([]xml.Attr, 0, len(attrs)) for _, k := range node.Attr { sortedAttrs = append(sortedAttrs, k) } @@ -145,7 +145,21 @@ func StructToXML(e *xml.Encoder, node *XMLNode, sorted bool) error { attrs = sortedAttrs } - st := xml.StartElement{Name: node.Name, Attr: attrs} + // Filter out default xmlns attributes that encoding/xml will regenerate + // from Name.Space. Without this, XML produced with literal xmlns + // attributes (rather than through encoding/xml's namespace-aware API) + // gets those declarations doubled on re-encode. Prefixed namespace + // declarations (xmlns:prefix="...") are preserved since the encoder + // does not regenerate those. + filtered := make([]xml.Attr, 0, len(attrs)) + for _, a := range attrs { + if a.Name.Space == "" && a.Name.Local == "xmlns" { + continue + } + filtered = append(filtered, a) + } + + st := xml.StartElement{Name: node.Name, Attr: filtered} e.EncodeToken(st) // return fmt.Errorf("encoder string : %s, %s, %s", node.Name.Local, node.Name.Space, st.Attr) diff --git a/trait.go b/trait.go new file mode 100644 index 000000000..a45db96c0 --- /dev/null +++ b/trait.go @@ -0,0 +1,21 @@ +package smithy + +// Trait represents a trait applied to a shape in a Smithy model. Traits +// related to (de)serialization are included in code-generated Schemas for the +// client. +type Trait interface { + TraitID() ShapeID +} + +// IndexableTrait is optionally implemented by Trait values that have a +// reserved index in Schema's indexed trait slice. All traits defined in the +// traits package implement this interface. +// +// You SHOULD NOT implement this outside of a smithy-go trait unless you know +// what you are doing. If you implement this and return a value that collides +// with one of the primary serde-based indexed traits (see index.go) you will +// probably break something. +type IndexableTrait interface { + Trait + TraitIndex() int +} diff --git a/traits/http.go b/traits/http.go new file mode 100644 index 000000000..b06e9fed1 --- /dev/null +++ b/traits/http.go @@ -0,0 +1,69 @@ +package traits + +import smithy "github.com/aws/smithy-go" + +// HTTPHeader represents smithy.api#httpHeader. +type HTTPHeader struct { + Name string +} + +// TraitID identifies the trait. +func (*HTTPHeader) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "httpHeader"} } + +// HTTPLabel represents smithy.api#httpLabel. +type HTTPLabel struct{} + +// TraitID identifies the trait. +func (*HTTPLabel) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "httpLabel"} } + +// HTTPPayload represents smithy.api#httpPayload. +type HTTPPayload struct{} + +// TraitID identifies the trait. +func (*HTTPPayload) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "httpPayload"} } + +// HTTPPrefixHeaders represents smithy.api#httpPrefixHeaders. +type HTTPPrefixHeaders struct { + Prefix string +} + +// TraitID identifies the trait. +func (*HTTPPrefixHeaders) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "httpPrefixHeaders"} } + +// HTTPQuery represents smithy.api#httpQuery. +type HTTPQuery struct { + Name string +} + +// TraitID identifies the trait. +func (*HTTPQuery) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "httpQuery"} } + +// HTTPQueryParams represents smithy.api#httpQueryParams. +type HTTPQueryParams struct{} + +// TraitID identifies the trait. +func (*HTTPQueryParams) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "httpQueryParams"} } + +// HTTPResponseCode represents smithy.api#httpResponseCode. +type HTTPResponseCode struct{} + +// TraitID identifies the trait. +func (*HTTPResponseCode) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "httpResponseCode"} } + +// HTTP represents smithy.api#http. +type HTTP struct { + Method string + URI string + Code int +} + +// TraitID identifies the trait. +func (*HTTP) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "http"} } + +// HTTPError represents smithy.api#httpError. +type HTTPError struct { + Code int +} + +// TraitID identifies the trait. +func (*HTTPError) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "httpError"} } diff --git a/traits/index.go b/traits/index.go new file mode 100644 index 000000000..47733afc6 --- /dev/null +++ b/traits/index.go @@ -0,0 +1,107 @@ +package traits + +// Trait index constants, ordered by frequency of occurrence across AWS API +// models. Lower indices are assigned to more common traits so that the +// per-schema indexed slice stays small. +const ( + indexJSONName = iota + indexHTTP + indexHTTPLabel + indexXMLName + indexHTTPQuery + indexEC2QueryName + indexHTTPError + indexHTTPHeader + indexSensitive + indexAWSQueryError + indexTimestampFormat + indexHTTPPayload + indexContextParam + indexHTTPResponseCode + indexHostLabel + indexXMLNamespace + indexXMLFlattened + indexStreaming + indexMediaType + indexHTTPQueryParams + indexEventPayload + indexHTTPPrefixHeaders + indexEventHeader + indexXMLAttribute + indexUnitShape +) + +// TraitIndex implements [smithy.IndexableTrait]. +func (*JSONName) TraitIndex() int { return indexJSONName } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*HTTP) TraitIndex() int { return indexHTTP } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*HTTPLabel) TraitIndex() int { return indexHTTPLabel } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*XMLName) TraitIndex() int { return indexXMLName } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*HTTPQuery) TraitIndex() int { return indexHTTPQuery } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*EC2QueryName) TraitIndex() int { return indexEC2QueryName } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*HTTPError) TraitIndex() int { return indexHTTPError } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*HTTPHeader) TraitIndex() int { return indexHTTPHeader } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*Sensitive) TraitIndex() int { return indexSensitive } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*AWSQueryError) TraitIndex() int { return indexAWSQueryError } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*TimestampFormat) TraitIndex() int { return indexTimestampFormat } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*HTTPPayload) TraitIndex() int { return indexHTTPPayload } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*ContextParam) TraitIndex() int { return indexContextParam } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*HTTPResponseCode) TraitIndex() int { return indexHTTPResponseCode } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*HostLabel) TraitIndex() int { return indexHostLabel } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*XMLNamespace) TraitIndex() int { return indexXMLNamespace } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*XMLFlattened) TraitIndex() int { return indexXMLFlattened } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*Streaming) TraitIndex() int { return indexStreaming } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*MediaType) TraitIndex() int { return indexMediaType } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*HTTPQueryParams) TraitIndex() int { return indexHTTPQueryParams } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*EventPayload) TraitIndex() int { return indexEventPayload } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*HTTPPrefixHeaders) TraitIndex() int { return indexHTTPPrefixHeaders } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*EventHeader) TraitIndex() int { return indexEventHeader } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*XMLAttribute) TraitIndex() int { return indexXMLAttribute } + +// TraitIndex implements [smithy.IndexableTrait]. +func (*UnitShape) TraitIndex() int { return indexUnitShape } diff --git a/traits/serde.go b/traits/serde.go new file mode 100644 index 000000000..25b7f0dd3 --- /dev/null +++ b/traits/serde.go @@ -0,0 +1,56 @@ +package traits + +import smithy "github.com/aws/smithy-go" + +// JSONName represents smithy.api#jsonName. +type JSONName struct { + Name string +} + +// TraitID identifies the trait. +func (*JSONName) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "jsonName"} } + +// MediaType represents smithy.api#mediaType. +type MediaType struct { + Type string +} + +// TraitID identifies the trait. +func (*MediaType) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "mediaType"} } + +// TimestampFormat represents smithy.api#timestampFormat. +type TimestampFormat struct { + Format string +} + +// TraitID identifies the trait. +func (*TimestampFormat) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "timestampFormat"} } + +// XMLAttribute represents smithy.api#xmlAttribute. +type XMLAttribute struct{} + +// TraitID identifies the trait. +func (*XMLAttribute) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "xmlAttribute"} } + +// XMLFlattened represents smithy.api#xmlFlattened. +type XMLFlattened struct{} + +// TraitID identifies the trait. +func (*XMLFlattened) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "xmlFlattened"} } + +// XMLName represents smithy.api#xmlName. +type XMLName struct { + Name string +} + +// TraitID identifies the trait. +func (*XMLName) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "xmlName"} } + +// XMLNamespace represents smithy.api#xmlNamespace. +type XMLNamespace struct { + URI string + Prefix string +} + +// TraitID identifies the trait. +func (*XMLNamespace) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "xmlNamespace"} } diff --git a/traits/traits.go b/traits/traits.go new file mode 100644 index 000000000..599be4e54 --- /dev/null +++ b/traits/traits.go @@ -0,0 +1,72 @@ +// Package traits defines representations of Smithy IDL traits that appear in +// code-generated schemas. +package traits + +import smithy "github.com/aws/smithy-go" + +// Sensitive represents smithy.api#sensitive. +type Sensitive struct{} + +// TraitID identifies the trait. +func (*Sensitive) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "sensitive"} } + +// EventHeader represents smithy.api#eventHeader. +type EventHeader struct{} + +// TraitID identifies the trait. +func (*EventHeader) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "eventHeader"} } + +// EventPayload represents smithy.api#eventPayload. +type EventPayload struct{} + +// TraitID identifies the trait. +func (*EventPayload) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "eventPayload"} } + +// Streaming represents smithy.api#streaming. +type Streaming struct{} + +// TraitID identifies the trait. +func (*Streaming) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "streaming"} } + +// HostLabel represents smithy.api#hostLabel. +type HostLabel struct{} + +// TraitID identifies the trait. +func (*HostLabel) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.api", Name: "hostLabel"} } + +// ContextParam represents smithy.rules#contextParam. +type ContextParam struct{} + +// TraitID identifies the trait. +func (*ContextParam) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.rules", Name: "contextParam"} } + +// AWSQueryError represents aws.protocols#awsQueryError. +type AWSQueryError struct { + ErrorCode string + StatusCode int +} + +// TraitID identifies the trait. +func (*AWSQueryError) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "aws.protocols", Name: "awsQueryError"} } + +// EC2QueryName represents aws.protocols#ec2QueryName. +type EC2QueryName struct { + Name string +} + +// TraitID identifies the trait. +func (*EC2QueryName) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "aws.protocols", Name: "ec2QueryName"} } + +// AWSQueryCompatible represents aws.protocols#awsQueryCompatible. +type AWSQueryCompatible struct{} + +// TraitID identifies the trait. +func (*AWSQueryCompatible) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "aws.protocols", Name: "awsQueryCompatible"} } + +// UnitShape is a synthetic trait applied to input/output shapes that were +// backfilled from Unit. It indicates the shape has no defined members and +// should be treated as absent for protocol serialization purposes. +type UnitShape struct{} + +// TraitID identifies the trait. +func (*UnitShape) TraitID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.go", Name: "unitShape"} } diff --git a/transport/http/auth.go b/transport/http/auth.go index 58e1ab5ef..5b5adad0b 100644 --- a/transport/http/auth.go +++ b/transport/http/auth.go @@ -5,6 +5,7 @@ import ( smithy "github.com/aws/smithy-go" "github.com/aws/smithy-go/auth" + "github.com/aws/smithy-go/eventstream" ) // AuthScheme defines an HTTP authentication scheme. @@ -19,3 +20,11 @@ type AuthScheme interface { type Signer interface { SignRequest(context.Context, *Request, auth.Identity, smithy.Properties) error } + +// EventStreamSigner is an optional interface that a [Signer] can implement to +// support signing of event stream messages. If the resolved auth scheme's +// signer implements this interface, the event stream middleware will use it to +// wrap the outbound message stream with a signing layer. +type EventStreamSigner interface { + NewMessageSigner(ctx context.Context, r *Request, identity auth.Identity, props smithy.Properties) (eventstream.MessageSigner, error) +} diff --git a/transport/http/eventstream.go b/transport/http/eventstream.go new file mode 100644 index 000000000..251db8ac3 --- /dev/null +++ b/transport/http/eventstream.go @@ -0,0 +1,209 @@ +package http + +import ( + "context" + "fmt" + "io" + "sync" + + "github.com/aws/smithy-go" + smithysync "github.com/aws/smithy-go/sync" +) + +// EventStreamWriter writes events to a stream using a ClientProtocol. +// +// The writer manages a background goroutine that facilitates the write loop. +// Calls to Send() on a writer will block until the message has been written. +// +// The writer doesn't know anything about signing. If event stream messages are +// getting signed by the client then the underlying io.Writer has already been +// wrapped to handle that at this point. +type EventStreamWriter struct { + protocol ClientProtocol + schema *smithy.Schema + + eventStream io.WriteCloser + stream chan singleflight + done chan struct{} + err *smithysync.OnceErr + + closeOnce sync.Once +} + +// we send one message at a time, the underlying write loop marshals these into +// the writer and reports back any error to the error channel +type singleflight struct { + variant *smithy.Schema + event smithy.Serializable + errCh chan<- error +} + +// NewEventStreamWriter returns an EventStreamWriter for the given schema. +func NewEventStreamWriter(protocol ClientProtocol, schema *smithy.Schema, stream io.WriteCloser) *EventStreamWriter { + w := &EventStreamWriter{ + protocol: protocol, + schema: schema, + + eventStream: stream, + stream: make(chan singleflight), + done: make(chan struct{}), + err: smithysync.NewOnceErr(), + } + + go w.writeStream() + + return w +} + +func (w *EventStreamWriter) writeStream() { + defer w.Close() + + for { + select { + case ev := <-w.stream: + err := w.protocol.SerializeEventMessage(w.schema, ev.variant, ev.event, w.eventStream) + if err != nil { + w.err.SetError(err) + } + ev.errCh <- err + case <-w.done: + return + } + } +} + +// Send writes a single event to the stream. +func (w *EventStreamWriter) Send(ctx context.Context, variant *smithy.Schema, event smithy.Serializable) error { + if err := w.err.Err(); err != nil { + return err + } + + errCh := make(chan error, 1) + select { + case w.stream <- singleflight{variant, event, errCh}: + case <-ctx.Done(): + return ctx.Err() + case <-w.done: + return fmt.Errorf("stream closed, unable to send event") + } + + select { + case err := <-errCh: + return err + case <-ctx.Done(): + return ctx.Err() + case <-w.done: + return fmt.Errorf("stream closed, unable to send event") + } +} + +// Close signals end-of-stream and closes the underlying writer. Close is +// safe for concurrent calls. +func (w *EventStreamWriter) Close() error { + w.closeOnce.Do(func() { + close(w.done) + w.err.SetError(w.eventStream.Close()) + }) + return w.err.Err() +} + +// Err returns the first error encountered during writing. +func (w *EventStreamWriter) Err() error { + return w.err.Err() +} + +// ErrorSet returns a channel that is closed when an error occurs. +func (w *EventStreamWriter) ErrorSet() <-chan struct{} { + return w.err.ErrorSet() +} + +// EventStreamReader reads events from a stream using a ClientProtocol. +type EventStreamReader struct { + protocol ClientProtocol + schema *smithy.Schema + types *smithy.TypeRegistry + + eventStream io.ReadCloser + stream chan smithy.Deserializable + done chan struct{} + err *smithysync.OnceErr + + closeOnce sync.Once +} + +// NewEventStreamReader returns an EventStreamReader that deserializes events +// through the given protocol from r. The schema is the event stream union +// schema. +func NewEventStreamReader(protocol ClientProtocol, schema *smithy.Schema, types *smithy.TypeRegistry, stream io.ReadCloser) *EventStreamReader { + r := &EventStreamReader{ + protocol: protocol, + schema: schema, + types: types, + + eventStream: stream, + stream: make(chan smithy.Deserializable), + done: make(chan struct{}), + err: smithysync.NewOnceErr(), + } + + go r.readEventStream() + + return r +} + +func (r *EventStreamReader) readEventStream() { + defer r.Close() + defer close(r.stream) + + for { + event, err := r.protocol.DeserializeEventMessage(r.schema, r.types, r.eventStream) + if err != nil { + if err == io.EOF { + return + } + select { + case <-r.done: + return + default: + r.err.SetError(err) + return + } + } + + select { + case r.stream <- event: + case <-r.done: + return + } + } +} + +// Events returns the channel from which deserialized events can be read. +func (r *EventStreamReader) Events() <-chan smithy.Deserializable { + return r.stream +} + +// Close stops the reader and releases the underlying stream. Close is safe +// for concurrent calls. +func (r *EventStreamReader) Close() error { + r.closeOnce.Do(func() { + close(r.done) + r.eventStream.Close() + }) + return r.err.Err() +} + +// Err returns the first error encountered during reading. +func (r *EventStreamReader) Err() error { + return r.err.Err() +} + +// ErrorSet returns a channel that is closed when an error occurs. +func (r *EventStreamReader) ErrorSet() <-chan struct{} { + return r.err.ErrorSet() +} + +// Closed returns a channel that is closed when the reader is closed. +func (r *EventStreamReader) Closed() <-chan struct{} { + return r.done +} diff --git a/transport/http/eventstream_middleware.go b/transport/http/eventstream_middleware.go new file mode 100644 index 000000000..f7d60dc76 --- /dev/null +++ b/transport/http/eventstream_middleware.go @@ -0,0 +1,69 @@ +package http + +import ( + "context" + "fmt" + "io" + + "github.com/aws/smithy-go/middleware" +) + +type eventStreamWriterKey struct{} + +// GetInputStreamWriter returns the io.WriteCloser pipe used for the +// operation's input event stream. +func GetInputStreamWriter(ctx context.Context) io.WriteCloser { + writeCloser, _ := middleware.GetStackValue(ctx, eventStreamWriterKey{}).(io.WriteCloser) + return writeCloser +} + +func setInputStreamWriter(ctx context.Context, writeCloser io.WriteCloser) context.Context { + return middleware.WithStackValue(ctx, eventStreamWriterKey{}, writeCloser) +} + +// InitializeStreamWriter is a Finalize middleware that creates an in-memory +// pipe and sets it as the HTTP request body so event stream messages can be +// written after the request is sent. +type InitializeStreamWriter struct{} + +// AddInitializeStreamWriter adds the InitializeStreamWriter middleware to the +// provided stack. +func AddInitializeStreamWriter(stack *middleware.Stack) error { + return stack.Finalize.Add(&InitializeStreamWriter{}, middleware.After) +} + +// ID returns the identifier for the middleware. +func (i *InitializeStreamWriter) ID() string { + return "InitializeStreamWriter" +} + +// HandleFinalize is the middleware implementation. +func (i *InitializeStreamWriter) HandleFinalize( + ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler, +) ( + out middleware.FinalizeOutput, metadata middleware.Metadata, err error, +) { + request, ok := in.Request.(*Request) + if !ok { + return out, metadata, fmt.Errorf("unknown transport type: %T", in.Request) + } + + inputReader, inputWriter := io.Pipe() + defer func() { + if err == nil { + return + } + _ = inputReader.Close() + _ = inputWriter.Close() + }() + + request, err = request.SetStream(inputReader) + if err != nil { + return out, metadata, err + } + in.Request = request + + ctx = setInputStreamWriter(ctx, inputWriter) + + return next.HandleFinalize(ctx, in) +} diff --git a/transport/http/protocol.go b/transport/http/protocol.go new file mode 100644 index 000000000..80fc9e6f9 --- /dev/null +++ b/transport/http/protocol.go @@ -0,0 +1,27 @@ +package http + +import ( + "context" + "io" + + "github.com/aws/smithy-go" +) + +// ClientProtocol defines the interface through which client-side operation +// request/responses are (de)serialized across the wire. +// +// While a caller CAN define their own protocol, it is almost never necessary +// to do so. In practice, a generated client will utilize one of the predefined +// protocols implemented as part of the Smithy client runtime. +type ClientProtocol interface { + ID() smithy.ShapeID + SerializeRequest(context.Context, *smithy.OperationSchema, smithy.Serializable, *Request) error + DeserializeResponse(ctx context.Context, schema *smithy.OperationSchema, types *smithy.TypeRegistry, resp *Response, out smithy.Deserializable) error + + // event stream APIs + HasInitialEventMessage() bool + SerializeEventMessage(schema, variant *smithy.Schema, v smithy.Serializable, w io.Writer) error + DeserializeEventMessage(schema *smithy.Schema, types *smithy.TypeRegistry, r io.Reader) (smithy.Deserializable, error) + SerializeInitialRequest(schema *smithy.Schema, v smithy.Serializable, w io.Writer) error + DeserializeInitialResponse(schema *smithy.Schema, r io.Reader, out smithy.Deserializable) error +} diff --git a/transport/http/protocol/awsjson/awsjson.go b/transport/http/protocol/awsjson/awsjson.go new file mode 100644 index 000000000..43e285744 --- /dev/null +++ b/transport/http/protocol/awsjson/awsjson.go @@ -0,0 +1,268 @@ +package awsjson + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/aws/smithy-go" + internaljson "github.com/aws/smithy-go/transport/http/protocol/internal/json" + internalerrors "github.com/aws/smithy-go/internal/errors" + internales "github.com/aws/smithy-go/internal/eventstream" + smithyio "github.com/aws/smithy-go/io" + "github.com/aws/smithy-go/middleware" + "github.com/aws/smithy-go/traits" + smithyhttp "github.com/aws/smithy-go/transport/http" +) + +// ProtocolOptions configures aws.protocols#awsJson1_0. +type ProtocolOptions struct{} + +// New10 returns an instance of the awsJson 1.0 protocol. +func New10(service *smithy.ServiceSchema, opts ...func(*ProtocolOptions)) *Protocol { + var o ProtocolOptions + for _, fn := range opts { + fn(&o) + } + _, qc := smithy.SchemaTrait[*traits.AWSQueryCompatible](service.Schema) + return &Protocol{ + version: "1.0", + queryCompatible: qc, + serviceName: service.Schema.ID().Name, + eventstream: &internales.Codec{ + Serializer: func() smithy.ShapeSerializer { return internaljson.NewShapeSerializer() }, + Deserializer: func(p []byte) smithy.ShapeDeserializer { return internaljson.NewShapeDeserializer(p) }, + ContentType: "application/json", + }, + } +} + +// New11 returns an instance of the awsJson 1.1 protocol. +func New11(service *smithy.ServiceSchema, opts ...func(*ProtocolOptions)) *Protocol { + var o ProtocolOptions + for _, fn := range opts { + fn(&o) + } + _, qc := smithy.SchemaTrait[*traits.AWSQueryCompatible](service.Schema) + return &Protocol{ + version: "1.1", + queryCompatible: qc, + serviceName: service.Schema.ID().Name, + eventstream: &internales.Codec{ + Serializer: func() smithy.ShapeSerializer { return internaljson.NewShapeSerializer() }, + Deserializer: func(p []byte) smithy.ShapeDeserializer { return internaljson.NewShapeDeserializer(p) }, + ContentType: "application/json", + }, + } +} + +// Protocol implements aws.protocols#awsJson1_0 and aws.protocols#awsJson1_1. +type Protocol struct { + version string + queryCompatible bool + serviceName string + + eventstream *internales.Codec +} + +var _ smithyhttp.ClientProtocol = (*Protocol)(nil) + +// ID identifies the protocol. +func (p *Protocol) ID() smithy.ShapeID { + if p.version == "1.1" { + return smithy.ShapeID{Namespace: "aws.protocols", Name: "awsJson1_1"} + } + return smithy.ShapeID{Namespace: "aws.protocols", Name: "awsJson1_0"} +} + +// SerializeRequest serializes a request for AWS Json 1.0. +func (p *Protocol) SerializeRequest( + ctx context.Context, + schema *smithy.OperationSchema, + in smithy.Serializable, + req *smithyhttp.Request, +) error { + req.Method = http.MethodPost + if len(req.URL.Path) == 0 { + req.URL.Path = "/" + } + req.Header.Set("X-Amz-Target", fmt.Sprintf("%s.%s", p.serviceName, middleware.GetOperationName(ctx))) + if schema.IsInputEventStream() { + req.Header.Set("Content-Type", "application/vnd.amazon.eventstream") + return nil + } + + req.Header.Set("Content-Type", "application/x-amz-json-"+p.version) + if p.queryCompatible { + req.Header.Set("X-Amzn-Query-Mode", "true") + } + + if schema.Input == nil { + sreq, err := req.SetStream(bytes.NewReader([]byte("{}"))) + if err != nil { + return fmt.Errorf("set stream: %w", err) + } + *req = *sreq + return nil + } + + ss := internaljson.NewShapeSerializer() + in.Serialize(ss) + + sreq, err := req.SetStream(bytes.NewReader(ss.Bytes())) + if err != nil { + return fmt.Errorf("set stream: %w", err) + } + + *req = *sreq + return nil +} + +// DeserializeResponse deserializes a response for AWS Json 1.0. +func (p *Protocol) DeserializeResponse( + ctx context.Context, + schema *smithy.OperationSchema, + types *smithy.TypeRegistry, + resp *smithyhttp.Response, + out smithy.Deserializable, +) error { + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return p.deserializeError(types, resp) + } + + if schema.IsOutputEventStream() { + if schema.IsInputEventStream() { + if err := p.eventstream.RequireBidiHTTP2(resp.Proto, resp.ProtoMajor); err != nil { + return err + } + } + return nil + } + + if schema.Output == nil { + return nil + } + + payload, err := io.ReadAll(resp.Body) + if err != nil { + return &smithy.DeserializationError{Err: err} + } + + if len(payload) == 0 { + return nil + } + + sd := internaljson.NewShapeDeserializer(payload) + if err := out.Deserialize(sd); err != nil { + return &smithy.DeserializationError{Err: err} + } + + return nil +} + +// HasInitialEventMessage is true because this is an RPC protocol. +func (*Protocol) HasInitialEventMessage() bool { + return true +} + +// SerializeEventMessage implements [smithyhttp.ClientProtocol]. +func (p *Protocol) SerializeEventMessage(schema, variant *smithy.Schema, v smithy.Serializable, w io.Writer) error { + return p.eventstream.SerializeEventMessage(schema, variant, v, w) +} + +// DeserializeEventMessage implements [smithyhttp.ClientProtocol]. +func (p *Protocol) DeserializeEventMessage(schema *smithy.Schema, types *smithy.TypeRegistry, r io.Reader) (smithy.Deserializable, error) { + return p.eventstream.DeserializeEventMessage(schema, types, r) +} + +// SerializeInitialRequest implements [smithyhttp.ClientProtocol]. +func (p *Protocol) SerializeInitialRequest(schema *smithy.Schema, v smithy.Serializable, w io.Writer) error { + return p.eventstream.SerializeInitialRequest(schema, v, w) +} + +// DeserializeInitialResponse implements [smithyhttp.ClientProtocol]. +func (p *Protocol) DeserializeInitialResponse(schema *smithy.Schema, r io.Reader, out smithy.Deserializable) error { + return p.eventstream.DeserializeInitialResponse(schema, r, out) +} + +// this handles both awsJson 1.0 and 1.1 - the only thing that 1.1 adds is +// error shape renaming (basically not having the namespace) but both versions +// of the protocol are supposed to support this anyway so it doesn't matter +func (p *Protocol) deserializeError(types *smithy.TypeRegistry, response *smithyhttp.Response) error { + var errorBuffer bytes.Buffer + if _, err := io.Copy(&errorBuffer, response.Body); err != nil { + return &smithy.DeserializationError{Err: fmt.Errorf("failed to copy error response body, %w", err)} + } + errorBody := bytes.NewReader(errorBuffer.Bytes()) + + errorCode := "UnknownError" + errorMessage := errorCode + + var headerCode string + headerCode = response.Header.Get("X-Amzn-ErrorType") + + var buff [1024]byte + ringBuffer := smithyio.NewRingBuffer(buff[:]) + + body := io.TeeReader(errorBody, ringBuffer) + decoder := json.NewDecoder(body) + decoder.UseNumber() + bodyInfo, err := internaljson.GetProtocolErrorInfo(decoder) + if err != nil { + var snapshot bytes.Buffer + io.Copy(&snapshot, ringBuffer) + err = &smithy.DeserializationError{ + Err: fmt.Errorf("failed to decode response body, %w", err), + Snapshot: snapshot.Bytes(), + } + return err + } + + errorBody.Seek(0, io.SeekStart) + if typ, ok := internaljson.ResolveProtocolErrorType(headerCode, bodyInfo); ok { + errorCode = typ + } + if len(bodyInfo.Message) != 0 { + errorMessage = bodyInfo.Message + } + + errorCode = internaljson.SanitizeErrorCode(errorCode) + + var queryCode string + var queryFault smithy.ErrorFault + if p.queryCompatible { + queryHeader := response.Header.Get("X-Amzn-Query-Error") + queryCode, queryFault = internalerrors.ParseQueryError(queryHeader) + } + + perr, ok := types.DeserializableError(errorCode) + if !ok { + code := errorCode + if queryCode != "" { + code = queryCode + } + return &smithy.GenericAPIError{ + Code: code, + Message: errorMessage, + Fault: queryFault, + } + } + + errorBody.Seek(0, io.SeekStart) + errorBytes, _ := io.ReadAll(errorBody) + if len(errorBytes) > 0 { + deser := internaljson.NewShapeDeserializer(errorBytes) + if err := perr.Deserialize(deser); err != nil { + return &smithy.DeserializationError{Err: err} + } + } + + if queryCode != "" { + internalerrors.SetErrorCodeOverride(perr, queryCode) + } + + return perr +} diff --git a/transport/http/protocol/awsquery/awsquery.go b/transport/http/protocol/awsquery/awsquery.go new file mode 100644 index 000000000..1af1b25f4 --- /dev/null +++ b/transport/http/protocol/awsquery/awsquery.go @@ -0,0 +1,186 @@ +package awsquery + +import ( + "bytes" + "context" + "fmt" + "io" + "net/http" + + "github.com/aws/smithy-go" + internalquery "github.com/aws/smithy-go/transport/http/protocol/internal/query" + internalxml "github.com/aws/smithy-go/transport/http/protocol/internal/xml" + internales "github.com/aws/smithy-go/internal/eventstream" + "github.com/aws/smithy-go/middleware" + "github.com/aws/smithy-go/traits" + smithyhttp "github.com/aws/smithy-go/transport/http" +) + +// ProtocolOptions configures aws.protocols#awsQuery. +type ProtocolOptions struct{} + +// Protocol implements aws.protocols#awsQuery. +type Protocol struct { + eventstream internales.NoEventStream + + version string +} + +var _ smithyhttp.ClientProtocol = (*Protocol)(nil) + +// New returns an instance of the awsQuery protocol. The service version is +// pulled from the ServiceVersion trait on the service schema. +func New(service *smithy.ServiceSchema, opts ...func(*ProtocolOptions)) *Protocol { + var o ProtocolOptions + for _, fn := range opts { + fn(&o) + } + return &Protocol{version: service.Version} +} + +// ID identifies the protocol. +func (*Protocol) ID() smithy.ShapeID { + return smithy.ShapeID{Namespace: "aws.protocols", Name: "awsQuery"} +} + +// HasInitialEventMessage implements [smithyhttp.ClientProtocol]. +func (p *Protocol) HasInitialEventMessage() bool { + return p.eventstream.HasInitialEventMessage() +} + +// SerializeEventMessage implements [smithyhttp.ClientProtocol]. +func (p *Protocol) SerializeEventMessage(schema, variant *smithy.Schema, v smithy.Serializable, w io.Writer) error { + return p.eventstream.SerializeEventMessage(schema, variant, v, w) +} + +// DeserializeEventMessage implements [smithyhttp.ClientProtocol]. +func (p *Protocol) DeserializeEventMessage(schema *smithy.Schema, types *smithy.TypeRegistry, r io.Reader) (smithy.Deserializable, error) { + return p.eventstream.DeserializeEventMessage(schema, types, r) +} + +// SerializeInitialRequest implements [smithyhttp.ClientProtocol]. +func (p *Protocol) SerializeInitialRequest(schema *smithy.Schema, v smithy.Serializable, w io.Writer) error { + return p.eventstream.SerializeInitialRequest(schema, v, w) +} + +// DeserializeInitialResponse implements [smithyhttp.ClientProtocol]. +func (p *Protocol) DeserializeInitialResponse(schema *smithy.Schema, r io.Reader, out smithy.Deserializable) error { + return p.eventstream.DeserializeInitialResponse(schema, r, out) +} + +// SerializeRequest serializes a request for awsQuery. +func (p *Protocol) SerializeRequest( + ctx context.Context, + schema *smithy.OperationSchema, + in smithy.Serializable, + req *smithyhttp.Request, +) error { + req.Method = http.MethodPost + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + if len(req.URL.Path) == 0 { + req.URL.Path = "/" + } + + ss := internalquery.NewShapeSerializer(middleware.GetOperationName(ctx), p.version) + if schema.Input != nil { + in.Serialize(ss) + } + + sreq, err := req.SetStream(bytes.NewReader(ss.Bytes())) + if err != nil { + return fmt.Errorf("set stream: %w", err) + } + + *req = *sreq + return nil +} + +// DeserializeResponse deserializes a response for awsQuery. +func (p *Protocol) DeserializeResponse( + ctx context.Context, + schema *smithy.OperationSchema, + types *smithy.TypeRegistry, + resp *smithyhttp.Response, + out smithy.Deserializable, +) error { + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return p.deserializeError(types, resp) + } + + payload, err := io.ReadAll(resp.Body) + if err != nil { + return &smithy.DeserializationError{Err: err} + } + + if len(payload) == 0 { + return nil + } + + inner, err := internalxml.ExtractElement(payload, middleware.GetOperationName(ctx)+"Result") + if err != nil { + if schema.Output == nil { + return nil + } + return &smithy.DeserializationError{Err: err} + } + + if len(inner) == 0 { + return nil + } + + sd := internalxml.NewShapeDeserializer(inner) + return out.Deserialize(sd) +} + +func (p *Protocol) deserializeError(types *smithy.TypeRegistry, resp *smithyhttp.Response) error { + payload, err := io.ReadAll(resp.Body) + if err != nil { + return &smithy.DeserializationError{Err: err} + } + + errorCode, errorMessage, errorBody, err := internalxml.GetProtocolErrorInfo(payload) + if err != nil { + return &smithy.DeserializationError{Err: err} + } + + // resolveError checks both direct shape name and @awsQueryError trait. + perr, ok := resolveError(types, errorCode) + if !ok { + return &smithy.GenericAPIError{ + Code: errorCode, + Message: errorMessage, + } + } + + if len(errorBody) > 0 { + sd := internalxml.NewShapeDeserializer(errorBody) + if err := perr.Deserialize(sd); err != nil { + return &smithy.DeserializationError{Err: err} + } + } + + return perr +} + +func resolveError(types *smithy.TypeRegistry, code string) (smithy.DeserializableError, bool) { + if perr, ok := types.DeserializableError(code); ok { + return perr, true + } + + for _, entry := range types.Entries { + if entry.Schema == nil { + continue + } + + if t, ok := smithy.SchemaTrait[*traits.AWSQueryError](entry.Schema); ok { + if t.ErrorCode == code { + v := entry.New() + if perr, ok := v.(smithy.DeserializableError); ok { + return perr, true + } + } + } + } + + return nil, false +} diff --git a/transport/http/protocol/ec2query/ec2query.go b/transport/http/protocol/ec2query/ec2query.go new file mode 100644 index 000000000..e3a7f72ab --- /dev/null +++ b/transport/http/protocol/ec2query/ec2query.go @@ -0,0 +1,161 @@ +package ec2query + +import ( + "bytes" + "context" + "fmt" + "io" + "net/http" + + "github.com/aws/smithy-go" + internalquery "github.com/aws/smithy-go/transport/http/protocol/internal/query" + internalxml "github.com/aws/smithy-go/transport/http/protocol/internal/xml" + internales "github.com/aws/smithy-go/internal/eventstream" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" +) + +// ProtocolOptions configures aws.protocols#ec2Query. +type ProtocolOptions struct{} + +// Protocol implements aws.protocols#ec2Query. +type Protocol struct { + eventstream internales.NoEventStream + + version string +} + +var _ smithyhttp.ClientProtocol = (*Protocol)(nil) + +// New returns an instance of the ec2Query protocol. The service version is +// pulled from the ServiceVersion trait on the service schema. +func New(service *smithy.ServiceSchema, opts ...func(*ProtocolOptions)) *Protocol { + var o ProtocolOptions + for _, fn := range opts { + fn(&o) + } + return &Protocol{version: service.Version} +} + +// ID identifies the protocol. +func (*Protocol) ID() smithy.ShapeID { + return smithy.ShapeID{Namespace: "aws.protocols", Name: "ec2Query"} +} + +// HasInitialEventMessage implements [smithyhttp.ClientProtocol]. +func (p *Protocol) HasInitialEventMessage() bool { + return p.eventstream.HasInitialEventMessage() +} + +// SerializeEventMessage implements [smithyhttp.ClientProtocol]. +func (p *Protocol) SerializeEventMessage(schema, variant *smithy.Schema, v smithy.Serializable, w io.Writer) error { + return p.eventstream.SerializeEventMessage(schema, variant, v, w) +} + +// DeserializeEventMessage implements [smithyhttp.ClientProtocol]. +func (p *Protocol) DeserializeEventMessage(schema *smithy.Schema, types *smithy.TypeRegistry, r io.Reader) (smithy.Deserializable, error) { + return p.eventstream.DeserializeEventMessage(schema, types, r) +} + +// SerializeInitialRequest implements [smithyhttp.ClientProtocol]. +func (p *Protocol) SerializeInitialRequest(schema *smithy.Schema, v smithy.Serializable, w io.Writer) error { + return p.eventstream.SerializeInitialRequest(schema, v, w) +} + +// DeserializeInitialResponse implements [smithyhttp.ClientProtocol]. +func (p *Protocol) DeserializeInitialResponse(schema *smithy.Schema, r io.Reader, out smithy.Deserializable) error { + return p.eventstream.DeserializeInitialResponse(schema, r, out) +} + +// SerializeRequest serializes a request for ec2Query. +func (p *Protocol) SerializeRequest( + ctx context.Context, + schema *smithy.OperationSchema, + in smithy.Serializable, + req *smithyhttp.Request, +) error { + req.Method = http.MethodPost + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + if len(req.URL.Path) == 0 { + req.URL.Path = "/" + } + + ss := internalquery.NewShapeSerializer(middleware.GetOperationName(ctx), p.version, + func(o *internalquery.ShapeSerializerOptions) { o.EC2Mode = true }, + ) + if schema.Input != nil { + in.Serialize(ss) + } + + sreq, err := req.SetStream(bytes.NewReader(ss.Bytes())) + if err != nil { + return fmt.Errorf("set stream: %w", err) + } + + *req = *sreq + return nil +} + +// DeserializeResponse deserializes a response for ec2Query. +func (p *Protocol) DeserializeResponse( + ctx context.Context, + schema *smithy.OperationSchema, + types *smithy.TypeRegistry, + resp *smithyhttp.Response, + out smithy.Deserializable, +) error { + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return p.deserializeError(types, resp) + } + + payload, err := io.ReadAll(resp.Body) + if err != nil { + return &smithy.DeserializationError{Err: err} + } + + if len(payload) == 0 { + return nil + } + + inner, err := internalxml.ExtractElement(payload, middleware.GetOperationName(ctx)+"Response") + if err != nil { + return &smithy.DeserializationError{Err: err} + } + + if len(inner) == 0 { + return nil + } + + sd := internalxml.NewShapeDeserializer(inner) + return out.Deserialize(sd) +} + +func (p *Protocol) deserializeError(types *smithy.TypeRegistry, resp *smithyhttp.Response) error { + payload, err := io.ReadAll(resp.Body) + if err != nil { + return &smithy.DeserializationError{Err: err} + } + + errorCode, errorMessage, errorBody, err := internalxml.GetProtocolErrorInfo(payload) + if err != nil { + return &smithy.DeserializationError{Err: err} + } + + // ec2query does not support @awsQueryError so this is a straight lookup + perr, ok := types.DeserializableError(errorCode) + if !ok { + return &smithy.GenericAPIError{ + Code: errorCode, + Message: errorMessage, + } + } + + if len(errorBody) > 0 { + sd := internalxml.NewShapeDeserializer(errorBody) + if err := perr.Deserialize(sd); err != nil { + return &smithy.DeserializationError{Err: err} + } + } + + return perr +} diff --git a/transport/http/protocol/internal/cbor/float16.go b/transport/http/protocol/internal/cbor/float16.go new file mode 100644 index 000000000..081eea705 --- /dev/null +++ b/transport/http/protocol/internal/cbor/float16.go @@ -0,0 +1,45 @@ +package cbor + +func float16to32(f uint16) uint32 { + sign, exp, mant := splitf16(f) + if exp == 0x1f { + return sign | 0xff<<23 | mant // infinity/NaN + } + + if exp == 0 { // subnormal + if mant == 0 { + return sign + } + return normalize(sign, mant) + } + + return sign | (exp+127-15)<<23 | mant // rebias exp by the difference between the two +} + +func splitf16(f uint16) (sign, exp, mantissa uint32) { + const smask = 0x1 << 15 // put sign in float32 position + const emask = 0x1f << 10 // pull exponent as a number (for bias shift) + const mmask = 0x3ff // put mantissa in float32 position + + return uint32(f&smask) << 16, uint32(f&emask) >> 10, uint32(f&mmask) << 13 +} + +// moves a float16 normal into normal float32 space +// to do this we must re-express the float16 mantissa in terms of a normal +// float32 where the hidden bit is 1, e.g. +// +// f16: 0 00000 0001010000 = 0.000101 * 2^(-14), which is equal to +// f32: 0 01101101 01000000000000000000000 = 1.01 * 2^(-18) +// +// this is achieved by shifting the mantissa to the right until the leading bit +// that == 1 reaches position 24, then the number of positions shifted over is +// equal to the offset from the subnormal exponent +func normalize(sign, mant uint32) uint32 { + exp := uint32(-14 + 127) // f16 subnormal exp, with f32 bias + for mant&0x800000 == 0 { // repeat until bit 24 ("hidden" mantissa) is 1 + mant <<= 1 + exp-- // tracking the offset + } + mant &= 0x7fffff // remask to 23bit + return sign | exp<<23 | mant +} diff --git a/transport/http/protocol/internal/cbor/shape_deserializer.go b/transport/http/protocol/internal/cbor/shape_deserializer.go new file mode 100644 index 000000000..eac54c112 --- /dev/null +++ b/transport/http/protocol/internal/cbor/shape_deserializer.go @@ -0,0 +1,729 @@ +package cbor + +import ( + "encoding/binary" + "errors" + "fmt" + "math" + "math/big" + "time" + + "github.com/aws/smithy-go" + "github.com/aws/smithy-go/document" + smithycbor "github.com/aws/smithy-go/encoding/cbor" + "github.com/aws/smithy-go/internal/serde" +) + +var errUnexpectedEOF = errors.New("unexpected end of CBOR data") + +// ShapeDeserializer implements unmarshaling of CBOR into Smithy shapes. +type ShapeDeserializer struct { + p []byte + off int + head serde.Stack[deserCtx] + opts ShapeDeserializerOptions +} + +type deserCtxKind byte + +const ( + deserCtxList deserCtxKind = iota + deserCtxMap + deserCtxStruct + deserCtxUnion +) + +type deserCtx struct { + kind deserCtxKind + schema *smithy.Schema + remaining int +} + +// ShapeDeserializerOptions configures ShapeDeserializer. +type ShapeDeserializerOptions struct{} + +var _ smithy.ShapeDeserializer = (*ShapeDeserializer)(nil) + +// NewShapeDeserializer creates a new ShapeDeserializer. +func NewShapeDeserializer(p []byte, opts ...func(*ShapeDeserializerOptions)) *ShapeDeserializer { + o := ShapeDeserializerOptions{} + for _, fn := range opts { + fn(&o) + } + return &ShapeDeserializer{p: p, head: serde.NewStack[deserCtx](), opts: o} +} + +func (d *ShapeDeserializer) eof() bool { + return d.off >= len(d.p) +} + +func (d *ShapeDeserializer) peekMajor() majorType { + return majorType(d.p[d.off] & 0xe0 >> 5) +} + +func (d *ShapeDeserializer) peekMinor() byte { + return d.p[d.off] & 0x1f +} + +func (d *ShapeDeserializer) readArg() (uint64, error) { + if d.eof() { + return 0, errUnexpectedEOF + } + + minor := d.peekMinor() + if minor < minorArg1 { + d.off++ + return uint64(minor), nil + } + + idx := int(minor - minorArg1) + if idx < 0 || idx >= len(argSizes) { + return 0, fmt.Errorf("unexpected minor value %d", minor) + } + + n := argSizes[idx] + if d.off+1+n > len(d.p) { + return 0, errUnexpectedEOF + } + + buf := d.p[d.off+1 : d.off+1+n] + d.off += 1 + n + + switch n { + case 1: + return uint64(buf[0]), nil + case 2: + return uint64(binary.BigEndian.Uint16(buf)), nil + case 4: + return uint64(binary.BigEndian.Uint32(buf)), nil + default: + return binary.BigEndian.Uint64(buf), nil + } +} + +func (d *ShapeDeserializer) readInt64() (int64, error) { + if d.eof() { + return 0, errUnexpectedEOF + } + + major := d.peekMajor() + switch major { + case majorTypeUint: + v, err := d.readArg() + if err != nil { + return 0, err + } + if v > math.MaxInt64 { + return 0, fmt.Errorf("cbor uint %d exceeds max int64", v) + } + return int64(v), nil + case majorTypeNegInt: + v, err := d.readArg() + if err != nil { + return 0, err + } + // CBOR negint: actual value is -1 - v + if v > math.MaxInt64 { + return 0, fmt.Errorf("cbor negint exceeds min int64") + } + return -1 - int64(v), nil + default: + return 0, fmt.Errorf("expected integer, got major type %d", major) + } +} + +func (d *ShapeDeserializer) readFloat64() (float64, error) { + if d.eof() { + return 0, errUnexpectedEOF + } + + major := d.peekMajor() + switch major { + case majorType7: + minor := d.peekMinor() + switch minor { + case major7Float16: + if d.off+3 > len(d.p) { + return 0, errUnexpectedEOF + } + bits := binary.BigEndian.Uint16(d.p[d.off+1 : d.off+3]) + d.off += 3 + return float64(math.Float32frombits(float16to32(bits))), nil + case major7Float32: + if d.off+5 > len(d.p) { + return 0, errUnexpectedEOF + } + bits := binary.BigEndian.Uint32(d.p[d.off+1 : d.off+5]) + d.off += 5 + return float64(math.Float32frombits(bits)), nil + case major7Float64: + if d.off+9 > len(d.p) { + return 0, errUnexpectedEOF + } + bits := binary.BigEndian.Uint64(d.p[d.off+1 : d.off+9]) + d.off += 9 + return math.Float64frombits(bits), nil + default: + return 0, fmt.Errorf("given majorType7, expected a minor of float type, instead got %d", minor) + } + case majorTypeUint: + v, err := d.readArg() + if err != nil { + return 0, err + } + return float64(v), nil + case majorTypeNegInt: + v, err := d.readArg() + if err != nil { + return 0, err + } + return -1 - float64(v), nil + default: + return 0, fmt.Errorf("expected float, got major type %d", major) + } +} + +// ReadNil implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadNil(s *smithy.Schema) (bool, error) { + if d.eof() { + return false, errUnexpectedEOF + } + + if d.peekMajor() == majorType7 { + minor := d.peekMinor() + if minor == major7Nil || minor == major7Undefined { + d.off++ + return true, nil + } + } + return false, nil +} + +// ReadInt8 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadInt8(s *smithy.Schema, v *int8) error { + return readInt(d, v, math.MinInt8, math.MaxInt8) +} + +// ReadInt16 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadInt16(s *smithy.Schema, v *int16) error { + return readInt(d, v, math.MinInt16, math.MaxInt16) +} + +// ReadInt32 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadInt32(s *smithy.Schema, v *int32) error { + return readInt(d, v, math.MinInt32, math.MaxInt32) +} + +// ReadInt64 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadInt64(s *smithy.Schema, v *int64) error { + return readInt(d, v, math.MinInt64, math.MaxInt64) +} + +// ReadFloat32 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadFloat32(s *smithy.Schema, v *float32) error { + return readFloat(d, v) +} + +// ReadFloat64 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadFloat64(s *smithy.Schema, v *float64) error { + return readFloat(d, v) +} + +// ReadBool implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadBool(s *smithy.Schema, v *bool) error { + if d.eof() { + return errUnexpectedEOF + } + if d.peekMajor() != majorType7 { + return fmt.Errorf("expected bool, got major type %d", d.peekMajor()) + } + minor := d.peekMinor() + switch minor { + case major7True: + *v = true + d.off++ + return nil + case major7False: + *v = false + d.off++ + return nil + default: + return fmt.Errorf("expected bool, got minor %d", minor) + } +} + +// ReadString implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadString(s *smithy.Schema, v *string) error { + if d.eof() { + return errUnexpectedEOF + } + + if d.peekMajor() != majorTypeString { + return fmt.Errorf("expected string, got major type %d", d.peekMajor()) + } + + if d.peekMinor() == minorIndefinite { + d.off++ + var result []byte + for d.off < len(d.p) && d.p[d.off] != 0xff { + if d.peekMajor() != majorTypeString { + return fmt.Errorf("expected string chunk, got major type %d", d.peekMajor()) + } + slen, err := d.readArg() + if err != nil { + return err + } + if d.off+int(slen) > len(d.p) { + return fmt.Errorf("string chunk length %d exceeds remaining data", slen) + } + result = append(result, d.p[d.off:d.off+int(slen)]...) + d.off += int(slen) + } + d.off++ // skip terminator + *v = string(result) + return nil + } + + slen, err := d.readArg() + if err != nil { + return err + } + if d.off+int(slen) > len(d.p) { + return fmt.Errorf("string length %d exceeds remaining data", slen) + } + *v = string(d.p[d.off : d.off+int(slen)]) + d.off += int(slen) + return nil +} + +// ReadTime implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadTime(schema *smithy.Schema, v *time.Time) error { + if d.eof() { + return errUnexpectedEOF + } + + if d.peekMajor() != majorTypeTag { + return fmt.Errorf("expected tag for timestamp, got major type %d", d.peekMajor()) + } + tagID, err := d.readArg() + if err != nil { + return err + } + if tagID != 1 { + return fmt.Errorf("expected tag 1 for timestamp, got %d", tagID) + } + + if d.eof() { + return errUnexpectedEOF + } + + major := d.peekMajor() + switch major { + case majorTypeUint, majorTypeNegInt: + secs, err := d.readInt64() + if err != nil { + return err + } + *v = time.Unix(secs, 0) + return nil + case majorType7: + f, err := d.readFloat64() + if err != nil { + return err + } + *v = time.UnixMilli(int64(f * 1e3)) + return nil + default: + return fmt.Errorf("unexpected major type %d in timestamp tag", major) + } +} + +// ReadBlob implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadBlob(s *smithy.Schema, v *[]byte) error { + if isNil, err := d.ReadNil(s); isNil || err != nil { + return err + } + + if d.peekMajor() != majorTypeSlice { + return fmt.Errorf("expected byte string, got major type %d", d.peekMajor()) + } + slen, err := d.readArg() + if err != nil { + return err + } + if d.off+int(slen) > len(d.p) { + return fmt.Errorf("blob length %d exceeds remaining data", slen) + } + *v = make([]byte, slen) + copy(*v, d.p[d.off:d.off+int(slen)]) + d.off += int(slen) + return nil +} + +// ReadList implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadList(s *smithy.Schema) error { + if d.eof() { + return errUnexpectedEOF + } + + if d.peekMajor() != majorTypeList { + return fmt.Errorf("expected list, got major type %d", d.peekMajor()) + } + if d.peekMinor() == minorIndefinite { + d.off++ + d.head.Push(deserCtx{kind: deserCtxList, remaining: -1}) + return nil + } + count, err := d.readArg() + if err != nil { + return err + } + d.head.Push(deserCtx{kind: deserCtxList, remaining: int(count)}) + return nil +} + +// ReadListItem implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadListItem(s *smithy.Schema) (bool, error) { + lc := d.head.Top() + if lc == nil || lc.kind != deserCtxList { + return false, fmt.Errorf("ReadListItem called without ReadList") + } + + if lc.remaining == -1 { + if d.off < len(d.p) && d.p[d.off] == 0xff { + d.off++ + d.head.Pop() + return false, nil + } + return true, nil + } + if lc.remaining <= 0 { + d.head.Pop() + return false, nil + } + lc.remaining-- + return true, nil +} + +// ReadMap implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadMap(s *smithy.Schema) error { + if d.eof() { + return errUnexpectedEOF + } + + if d.peekMajor() != majorTypeMap { + return fmt.Errorf("expected map, got major type %d", d.peekMajor()) + } + if d.peekMinor() == minorIndefinite { + d.off++ + d.head.Push(deserCtx{kind: deserCtxMap, remaining: -1}) + return nil + } + count, err := d.readArg() + if err != nil { + return err + } + d.head.Push(deserCtx{kind: deserCtxMap, remaining: int(count)}) + return nil +} + +// ReadMapKey implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadMapKey(s *smithy.Schema) (string, bool, error) { + mc := d.head.Top() + if mc == nil || mc.kind != deserCtxMap { + return "", false, errors.New("ReadMapKey called without ReadMap") + } + + if mc.remaining == -1 { + if d.off < len(d.p) && d.p[d.off] == 0xff { + d.off++ + d.head.Pop() + return "", false, nil + } + } else { + if mc.remaining <= 0 { + d.head.Pop() + return "", false, nil + } + mc.remaining-- + } + + var key string + if err := d.ReadString(nil, &key); err != nil { + return "", false, err + } + return key, true, nil +} + +// ReadStruct implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadStruct(s *smithy.Schema) error { + if isNil, err := d.ReadNil(s); isNil || err != nil { + return err + } + + if d.peekMajor() != majorTypeMap { + return fmt.Errorf("expected map for struct, got major type %d", d.peekMajor()) + } + if d.peekMinor() == minorIndefinite { + d.off++ + d.head.Push(deserCtx{kind: deserCtxStruct, schema: s, remaining: -1}) + return nil + } + count, err := d.readArg() + if err != nil { + return err + } + d.head.Push(deserCtx{kind: deserCtxStruct, schema: s, remaining: int(count)}) + return nil +} + +// ReadStructMember implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadStructMember() (*smithy.Schema, error) { + sc := d.head.Top() + if sc == nil || sc.kind != deserCtxStruct { + return nil, fmt.Errorf("ReadStructMember called without ReadStruct") + } + + if sc.remaining == -1 { + if d.off < len(d.p) && d.p[d.off] == 0xff { + d.off++ + d.head.Pop() + return nil, nil + } + } else { + if sc.remaining <= 0 { + d.head.Pop() + return nil, nil + } + sc.remaining-- + } + + var key string + if err := d.ReadString(nil, &key); err != nil { + return nil, err + } + + member := sc.schema.Member(key) + if member == nil { + if err := d.skip(); err != nil { + return nil, err + } + return d.ReadStructMember() + } + + if isNil, err := d.ReadNil(member); err != nil { + return nil, err + } else if isNil { + return d.ReadStructMember() + } + + return member, nil +} + +// ReadUnion implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadUnion(s *smithy.Schema) (*smithy.Schema, error) { + top := d.head.Top() + if top == nil || top.kind != deserCtxUnion { // first call: open the map + if d.eof() { + return nil, errUnexpectedEOF + } + if d.peekMajor() != majorTypeMap { + return nil, fmt.Errorf("expected map for union, got major type %d", d.peekMajor()) + } + if d.peekMinor() == minorIndefinite { + d.off++ + d.head.Push(deserCtx{kind: deserCtxUnion, schema: s, remaining: -1}) + } else { + count, err := d.readArg() + if err != nil { + return nil, err + } + d.head.Push(deserCtx{kind: deserCtxUnion, schema: s, remaining: int(count)}) + } + } + + uc := d.head.Top() + for { + if uc.remaining == -1 { + if d.off < len(d.p) && d.p[d.off] == 0xff { + d.off++ + d.head.Pop() + return nil, nil + } + } else if uc.remaining <= 0 { + d.head.Pop() + return nil, nil + } else { + uc.remaining-- + } + + var key string + if err := d.ReadString(nil, &key); err != nil { + return nil, err + } + + if d.off < len(d.p) && d.peekMajor() == majorType7 { + minor := d.peekMinor() + if minor == major7Nil || minor == major7Undefined { + d.off++ + continue + } + } + + member := s.Member(key) + if member == nil { + if err := d.skip(); err != nil { + return nil, err + } + continue + } + + return member, nil + } +} + +// ReadDocument is unimplemented and will panic. +func (d *ShapeDeserializer) ReadDocument(schema *smithy.Schema, v *document.Value) error { + panic("unimplemented") +} + +func (d *ShapeDeserializer) skip() error { + if d.eof() { + return errUnexpectedEOF + } + major := d.peekMajor() + switch major { + case majorTypeUint, majorTypeNegInt: + _, err := d.readArg() + return err + case majorTypeSlice, majorTypeString: + if d.peekMinor() == minorIndefinite { + return d.skipIndefiniteBytes() + } + slen, err := d.readArg() + if err != nil { + return err + } + d.off += int(slen) + return nil + case majorTypeList, majorTypeMap: + itemsPerEntry := 1 + if major == majorTypeMap { + itemsPerEntry = 2 + } + if d.peekMinor() == minorIndefinite { + d.off++ + for d.off < len(d.p) && d.p[d.off] != 0xff { + for range itemsPerEntry { + if err := d.skip(); err != nil { + return err + } + } + } + d.off++ // skip terminator + return nil + } + count, err := d.readArg() + if err != nil { + return err + } + for range int(count) * itemsPerEntry { + if err := d.skip(); err != nil { + return err + } + } + return nil + case majorTypeTag: + _, err := d.readArg() + if err != nil { + return err + } + return d.skip() // skip the tagged value + case majorType7: + minor := d.peekMinor() + n := 0 + if minor >= minorArg1 && minor <= minorArg8 { + n = major7ExtraSizes[minor-minorArg1] + } + d.off += 1 + n + return nil + default: + return fmt.Errorf("unexpected major type %d", major) + } +} + +func (d *ShapeDeserializer) skipIndefiniteBytes() error { + d.off++ // skip the indefinite marker + for d.off < len(d.p) && d.p[d.off] != 0xff { + if err := d.skip(); err != nil { + return err + } + } + d.off++ // skip break + return nil +} + +type tint interface { + int8 | int16 | int32 | int64 +} + +func readInt[T tint](d *ShapeDeserializer, v *T, min, max int64) error { + n, err := d.readInt64() + if err != nil { + return err + } + if n < min || n > max { + return fmt.Errorf("int %d exceeds %T range", n, *v) + } + *v = T(n) + return nil +} + +type tfloat interface { + float32 | float64 +} + +func readFloat[T tfloat](d *ShapeDeserializer, v *T) error { + n, err := d.readFloat64() + if err != nil { + return err + } + *v = T(n) + return nil +} + +// GetProtocolErrorInfo decodes error type/message from a CBOR response body. +func GetProtocolErrorInfo(p []byte) (typ, message string, err error) { + v, decErr := smithycbor.Decode(p) + if decErr != nil { + return "", "", decErr + } + + m, ok := v.(smithycbor.Map) + if !ok { + return "", "", nil + } + + if t, ok := m["__type"]; ok { + if s, ok := t.(smithycbor.String); ok { + typ = string(s) + } + } + if msg, ok := m["message"]; ok { + if s, ok := msg.(smithycbor.String); ok { + message = string(s) + } + } + + return typ, message, nil +} + +// ReadBigInt is unimplemented and will return an error. +func (d *ShapeDeserializer) ReadBigInt(_ *smithy.Schema, _ *big.Int) error { + return fmt.Errorf("unimplemented") +} + +// ReadBigFloat is unimplemented and will return an error. +func (d *ShapeDeserializer) ReadBigFloat(_ *smithy.Schema, _ *big.Float) error { + return fmt.Errorf("unimplemented") +} diff --git a/transport/http/protocol/internal/cbor/shape_serializer.go b/transport/http/protocol/internal/cbor/shape_serializer.go new file mode 100644 index 000000000..fe378d29b --- /dev/null +++ b/transport/http/protocol/internal/cbor/shape_serializer.go @@ -0,0 +1,354 @@ +package cbor + +import ( + "encoding/binary" + "math" + "math/big" + "time" + + "github.com/aws/smithy-go" + "github.com/aws/smithy-go/document" + "github.com/aws/smithy-go/traits" +) + +// ShapeSerializer implements marshaling of Smithy shapes to CBOR. +type ShapeSerializer struct { + buf []byte + head []byte + + opts ShapeSerializerOptions + + // rootSchema is the schema passed to the first WriteStruct call, + // used by the protocol to determine if the input is a Unit shape. + rootSchema *smithy.Schema +} + +// ShapeSerializerOptions configures ShapeSerializer. +type ShapeSerializerOptions struct{} + +var _ smithy.ShapeSerializer = (*ShapeSerializer)(nil) + +// NewShapeSerializer creates a new ShapeSerializer. +func NewShapeSerializer(opts ...func(*ShapeSerializerOptions)) *ShapeSerializer { + o := ShapeSerializerOptions{} + for _, fn := range opts { + fn(&o) + } + return &ShapeSerializer{opts: o} +} + +// Bytes returns the serialized CBOR bytes. +func (s *ShapeSerializer) Bytes() []byte { + return s.buf +} + +// IsUnitShape returns true if the serialized content represents a Unit shape +// (a struct with no defined input, marked with the UnitShape trait). +func (s *ShapeSerializer) IsUnitShape() bool { + if s.rootSchema == nil { + return false + } + _, ok := smithy.SchemaTrait[*traits.UnitShape](s.rootSchema) + return ok +} + +func (s *ShapeSerializer) top() byte { + if len(s.head) == 0 { + return 0xff + } + return s.head[len(s.head)-1] +} + +func (s *ShapeSerializer) push(v byte) { + s.head = append(s.head, v) +} + +func (s *ShapeSerializer) pop() { + s.head = s.head[:len(s.head)-1] +} + +// WriteBool implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteBool(schema *smithy.Schema, v bool) { + s.writeKey(schema) + if v { + s.buf = append(s.buf, compose(majorType7, major7True)) + } else { + s.buf = append(s.buf, compose(majorType7, major7False)) + } +} + +func (s *ShapeSerializer) writeInt(v int64) { + if v >= 0 { + s.writeUint(uint64(v)) + } else { + s.writeArg(majorTypeNegInt, uint64(-v-1)) + } +} + +func (s *ShapeSerializer) writeUint(v uint64) { + s.writeArg(majorTypeUint, v) +} + +// WriteInt8 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt8(schema *smithy.Schema, v int8) { + s.writeKey(schema) + s.writeInt(int64(v)) +} + +// WriteInt16 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt16(schema *smithy.Schema, v int16) { + s.writeKey(schema) + s.writeInt(int64(v)) +} + +// WriteInt32 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt32(schema *smithy.Schema, v int32) { + s.writeKey(schema) + s.writeInt(int64(v)) +} + +// WriteInt64 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt64(schema *smithy.Schema, v int64) { + s.writeKey(schema) + s.writeInt(v) +} + +// WriteFloat32 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteFloat32(schema *smithy.Schema, v float32) { + s.writeKey(schema) + s.buf = append(s.buf, compose(majorType7, major7Float32)) + s.buf = binary.BigEndian.AppendUint32(s.buf, math.Float32bits(v)) +} + +// WriteFloat64 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteFloat64(schema *smithy.Schema, v float64) { + s.writeKey(schema) + s.buf = append(s.buf, compose(majorType7, major7Float64)) + s.buf = binary.BigEndian.AppendUint64(s.buf, math.Float64bits(v)) +} + +// WriteString implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteString(schema *smithy.Schema, v string) { + s.writeKey(schema) + s.writeTextString(v) +} + +// WriteBlob implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteBlob(schema *smithy.Schema, v []byte) { + if v == nil { + return + } + s.writeKey(schema) + s.writeArg(majorTypeSlice, uint64(len(v))) + s.buf = append(s.buf, v...) +} + +// WriteList implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteList(schema *smithy.Schema) { + s.writeKey(schema) + if s.top() == ctxMapValue { + s.pop() + } + s.buf = append(s.buf, compose(majorTypeList, minorIndefinite)) + s.push(ctxList) +} + +// CloseList implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseList() { + if s.top() != ctxList { + return + } + s.pop() + s.buf = append(s.buf, 0xff) +} + +// WriteMap implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteMap(schema *smithy.Schema) { + if s.rootSchema == nil && len(s.head) == 0 { + s.rootSchema = schema + } + s.writeKey(schema) + if s.top() == ctxMapValue { + s.pop() + } + s.buf = append(s.buf, compose(majorTypeMap, minorIndefinite)) + s.push(ctxMap) +} + +// WriteKey implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteKey(_ *smithy.Schema, key string) { + s.writeTextString(key) + s.push(ctxMapValue) +} + +// CloseMap implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseMap() { + if s.top() != ctxMap { + return + } + s.pop() + s.buf = append(s.buf, 0xff) +} + +// WriteTime implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteTime(schema *smithy.Schema, v time.Time) { + s.writeKey(schema) + if s.top() == ctxMapValue { + s.pop() + } + + // rpcv2Cbor: always epoch-seconds as float64, tag 1 + epoch := float64(v.UnixMilli()) / 1e3 + s.writeArg(majorTypeTag, 1) + s.buf = append(s.buf, compose(majorType7, major7Float64)) + s.buf = binary.BigEndian.AppendUint64(s.buf, math.Float64bits(epoch)) +} + +// WriteUnion implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteUnion(schema, variant *smithy.Schema) { + s.writeKey(schema) + // union is a map with a single key + s.writeArg(majorTypeMap, 1) + s.writeTextString(variant.MemberName()) +} + +// CloseUnion implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseUnion() {} + +// WriteStruct implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteStruct(schema *smithy.Schema) { + if s.rootSchema == nil && len(s.head) == 0 { + s.rootSchema = schema + } + s.writeKey(schema) + if s.top() == ctxMapValue { + s.pop() + } + s.buf = append(s.buf, compose(majorTypeMap, minorIndefinite)) + s.push(ctxMap) +} + +// CloseStruct implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseStruct() { + s.pop() + s.buf = append(s.buf, 0xff) +} + +// WriteNil implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteNil(schema *smithy.Schema) { + s.writeKey(schema) + if s.top() == ctxMapValue { + s.pop() + } + s.buf = append(s.buf, compose(majorType7, major7Nil)) +} + +// WriteBigInt is unimplemented and will panic. +func (s *ShapeSerializer) WriteBigInt(schema *smithy.Schema, v *big.Int) { + panic("unimplemented") +} + +// WriteBigFloat is unimplemented and will panic. +func (s *ShapeSerializer) WriteBigFloat(schema *smithy.Schema, v *big.Float) { + panic("unimplemented") +} + +// WriteDocument is unimplemented and will panic. +func (s *ShapeSerializer) WriteDocument(schema *smithy.Schema, v document.Value) { + panic("unimplemented") +} + +// writeKey writes the member name as a CBOR text string key when inside a +// struct or map context. +func (s *ShapeSerializer) writeKey(schema *smithy.Schema) { + // If we're in a map value context (after WriteKey), just pop it and + // don't write a key - the key was already written by WriteKey. + if s.top() == ctxMapValue { + s.pop() + return + } + if schema == nil { + return + } + + if s.top() == ctxMap { + name := schema.MemberName() + if name != "" { + s.writeTextString(name) + } + } +} + +func (s *ShapeSerializer) writeTextString(v string) { + s.writeArg(majorTypeString, uint64(len(v))) + s.buf = append(s.buf, v...) +} + +func (s *ShapeSerializer) writeArg(major majorType, arg uint64) { + if arg < 24 { + s.buf = append(s.buf, byte(major)<<5|byte(arg)) + } else if arg < 0x100 { + s.buf = append(s.buf, compose(major, minorArg1), byte(arg)) + } else if arg < 0x10000 { + s.buf = append(s.buf, compose(major, minorArg2)) + s.buf = binary.BigEndian.AppendUint16(s.buf, uint16(arg)) + } else if arg < 0x100000000 { + s.buf = append(s.buf, compose(major, minorArg4)) + s.buf = binary.BigEndian.AppendUint32(s.buf, uint32(arg)) + } else { + s.buf = append(s.buf, compose(major, minorArg8)) + s.buf = binary.BigEndian.AppendUint64(s.buf, arg) + } +} + +// duplicated from the old encoding/cbor + +type majorType byte + +const ( + majorTypeUint majorType = 0 + majorTypeNegInt majorType = 1 + majorTypeSlice majorType = 2 + majorTypeString majorType = 3 + majorTypeList majorType = 4 + majorTypeMap majorType = 5 + majorTypeTag majorType = 6 + majorType7 majorType = 7 +) + +const ( + minorArg1 = 24 + minorArg2 = 25 + minorArg4 = 26 + minorArg8 = 27 + minorIndefinite = 31 +) + +const ( + major7False = 20 + major7True = 21 + major7Nil = 22 + major7Undefined = 23 + major7Float16 = minorArg2 + major7Float32 = minorArg4 + major7Float64 = minorArg8 +) + +// maps minor argument indicators (minorArg1..minorArg8) to the number of bytes +// that follow for the argument value +var argSizes = [4]int{1, 2, 4, 8} + +// maps minor values (minorArg1..minorArg8) in major type 7 to the number of +// payload bytes that follow +var major7ExtraSizes = [4]int{0, 2, 4, 8} + +func compose(major majorType, minor byte) byte { + return byte(major)<<5 | minor +} + +// context sentinels for the serialization state stack +const ( + ctxList byte = iota // inside a list + ctxMap // inside a map (struct or smithy map) + ctxMapValue // next write is a map value (after WriteKey) +) diff --git a/transport/http/protocol/internal/httpbinding/deserializer.go b/transport/http/protocol/internal/httpbinding/deserializer.go new file mode 100644 index 000000000..cdfa0d4ac --- /dev/null +++ b/transport/http/protocol/internal/httpbinding/deserializer.go @@ -0,0 +1,496 @@ +package httpbinding + +import ( + "encoding/base64" + "fmt" + "math/big" + "net/http" + "strconv" + "strings" + "time" + + "github.com/aws/smithy-go" + "github.com/aws/smithy-go/document" + "github.com/aws/smithy-go/traits" + smithyhttp "github.com/aws/smithy-go/transport/http" +) + +// ShapeDeserializer reads HTTP-bound output struct members from the response, +// delegates body members to the wrapped body deserializer. +type ShapeDeserializer struct { + response *http.Response + body smithy.ShapeDeserializer + payload []byte + + // ALL http binding traits are applied on the top-level output struct, for + // anything nested we are just delegating to the payload deserializer + depth int + topLevel *smithy.Schema + + // unlike an RPC-style protocol, members of the top-level output could be + // HTTP-bound, so we track that when ReadStruct is first called and "yield" + // them back to the caller through ReadStructMember + inBindings bool + bindings []*smithy.Schema + bindingIndex int + + inBody bool + hasPayload bool + + inHeaderList bool + headerListValues []string + headerListIdx int + + inPrefixMap bool + prefixValue string + prefixKeys []string + prefixKeyIdx int +} + +var _ smithy.ShapeDeserializer = (*ShapeDeserializer)(nil) + +// ShapeDeserializerOptions configures ShapeDeserializer. +type ShapeDeserializerOptions struct{} + +// NewShapeDeserializer creates a ShapeDeserializer for the given HTTP +// response. +// +// The payload should be nil in streaming-blob response operations. +func NewShapeDeserializer(resp *http.Response, body smithy.ShapeDeserializer, payload []byte, opts ...func(*ShapeDeserializerOptions)) *ShapeDeserializer { + return &ShapeDeserializer{ + response: resp, + body: body, + payload: payload, + } +} + +// ReadStruct implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadStruct(s *smithy.Schema) error { + d.depth++ + if d.depth > 1 { + return d.body.ReadStruct(s) + } + + d.topLevel = s + for _, member := range s.Members() { + if _, ok := smithy.SchemaTrait[*traits.HTTPPayload](member); ok { + d.hasPayload = true + } + if d.isBindingSet(member) { + d.bindings = append(d.bindings, member) + } + } + + return nil +} + +// ReadStructMember implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadStructMember() (*smithy.Schema, error) { + if d.depth > 1 { + ms, err := d.body.ReadStructMember() + if ms == nil { + d.depth-- + } + return ms, err + } + + if d.bindingIndex < len(d.bindings) { + member := d.bindings[d.bindingIndex] + d.bindingIndex++ + d.inBindings = true + return member, nil + } + d.inBindings = false + + if d.hasPayload { // i.e. no unbound members + d.depth-- + return nil, nil + } + + if !d.inBody { + d.inBody = true + if err := d.body.ReadStruct(d.topLevel); err != nil { + return nil, err + } + } + + ms, err := d.body.ReadStructMember() + if ms == nil { + d.depth-- + } + return ms, err +} + +// ReadString implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadString(s *smithy.Schema, v *string) error { + if d.inHeaderList { + *v = d.headerListValues[d.headerListIdx] + d.headerListIdx++ + return nil + } + if d.inBindings { + if _, ok := isHTTPHeader(s); ok { + hv, err := d.readHeaderString(s) + if err != nil { + return err + } + *v = hv + return nil + } + if _, ok := smithy.SchemaTrait[*traits.HTTPPayload](s); ok { + *v = string(d.payload) + return nil + } + } + if d.inPrefixMap { + key := d.prefixKeys[d.prefixKeyIdx-1] + *v = d.response.Header.Get(d.prefixValue + key) + return nil + } + return d.body.ReadString(s, v) +} + +// ReadBool implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadBool(s *smithy.Schema, v *bool) error { + if !d.inBindings { + return d.body.ReadBool(s, v) + } + + name, _ := isHTTPHeader(s) + + var hv string + if d.inHeaderList { + hv = d.nextHeaderValue() + } else { + hv = d.response.Header.Get(name) + } + + n, err := strconv.ParseBool(hv) + if err != nil { + return err + } + + *v = n + return nil +} + +// ReadInt8 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadInt8(s *smithy.Schema, v *int8) error { + if !d.inBindings { + return d.body.ReadInt8(s, v) + } + return readHeaderInt[int8](d, s, v) +} + +// ReadInt16 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadInt16(s *smithy.Schema, v *int16) error { + if !d.inBindings { + return d.body.ReadInt16(s, v) + } + return readHeaderInt[int16](d, s, v) +} + +// ReadInt32 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadInt32(s *smithy.Schema, v *int32) error { + if !d.inBindings { + return d.body.ReadInt32(s, v) + } + + // https://smithy.io/2.0/spec/http-bindings.html#httpresponsecode-trait + // + // The httpResponseCode trait can be applied to structure members that + // target an integer within any structure that has no input trait applied. + if _, ok := smithy.SchemaTrait[*traits.HTTPResponseCode](s); ok { + *v = int32(d.response.StatusCode) + return nil + } + + return readHeaderInt[int32](d, s, v) +} + +// ReadInt64 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadInt64(s *smithy.Schema, v *int64) error { + if !d.inBindings { + return d.body.ReadInt64(s, v) + } + return readHeaderInt[int64](d, s, v) +} + +// ReadFloat32 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadFloat32(s *smithy.Schema, v *float32) error { + if !d.inBindings { + return d.body.ReadFloat32(s, v) + } + return readHeaderFloat[float32](d, s, v) +} + +// ReadFloat64 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadFloat64(s *smithy.Schema, v *float64) error { + if !d.inBindings { + return d.body.ReadFloat64(s, v) + } + return readHeaderFloat[float64](d, s, v) +} + +// ReadTime implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadTime(s *smithy.Schema, v *time.Time) error { + if d.inHeaderList { + return d.readHeaderListTime(func(t time.Time) { *v = t }) + } + if d.inBindings { + t, err := d.readHeaderTime(s) + if err != nil { + return err + } + *v = t + return nil + } + return d.body.ReadTime(s, v) +} + +func (d *ShapeDeserializer) readHeaderTime(s *smithy.Schema) (time.Time, error) { + name, _ := isHTTPHeader(s) + hv := d.response.Header.Get(name) + t, err := parseTimestamp(s, "http-date", hv) + if err != nil { + return time.Time{}, err + } + return t, nil +} + +func (d *ShapeDeserializer) readHeaderListTime(assign func(time.Time)) error { + hv := d.headerListValues[d.headerListIdx] + d.headerListIdx++ + t, err := parseTimestamp(nil, "http-date", hv) + if err != nil { + return err + } + assign(t) + return nil +} + +// ReadBlob implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadBlob(s *smithy.Schema, v *[]byte) error { + if !d.inBindings { + return d.body.ReadBlob(s, v) + } + + if _, ok := smithy.SchemaTrait[*traits.HTTPPayload](s); ok { + *v = d.payload + return nil + } + + name, ok := isHTTPHeader(s) + if !ok { + return fmt.Errorf("ReadBlob: unhandled http binding") + } + + hv := d.response.Header.Get(name) + b, err := base64.StdEncoding.DecodeString(hv) + if err != nil { + return err + } + *v = b + return nil +} + +// ReadList implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadList(s *smithy.Schema) error { + if !d.inBindings { + return d.body.ReadList(s) + } + + name, ok := isHTTPHeader(s) + if !ok { + return fmt.Errorf("ReadList called outside of payload / http binding") + } + + d.inHeaderList = true + d.headerListIdx = 0 + if s.ListMember() != nil && s.ListMember().Type() == smithy.ShapeTypeTimestamp && timestampFormat(s.ListMember(), "http-date") == "http-date" { + vs, err := smithyhttp.SplitHTTPDateTimestampHeaderListValues(d.response.Header.Values(name)) + if err != nil { + return err + } + + d.headerListValues = vs + } else { + vs, err := smithyhttp.SplitHeaderListValues(d.response.Header.Values(name)) + if err != nil { + return err + } + + d.headerListValues = vs + } + return nil +} + +// ReadListItem implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadListItem(s *smithy.Schema) (bool, error) { + if !d.inHeaderList { + return d.body.ReadListItem(s) + } + + if d.headerListIdx >= len(d.headerListValues) { + d.inHeaderList = false + return false, nil + } + return true, nil +} + +// ReadMap implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadMap(s *smithy.Schema) error { + if !d.inBindings { + return d.body.ReadMap(s) + } + + ph, ok := smithy.SchemaTrait[*traits.HTTPPrefixHeaders](s) + if !ok { + return fmt.Errorf("ReadMap called outside of payload / http binding") + } + + d.inPrefixMap = true + d.prefixValue = ph.Prefix + d.prefixKeyIdx = 0 + + canon := http.CanonicalHeaderKey(ph.Prefix) + for name := range d.response.Header { + if len(name) > len(canon) && strings.EqualFold(name[:len(canon)], canon) { + d.prefixKeys = append(d.prefixKeys, strings.ToLower(name[len(canon):])) + } + } + + return nil +} + +// ReadMapKey implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadMapKey(s *smithy.Schema) (string, bool, error) { + if !d.inPrefixMap { + return d.body.ReadMapKey(s) + } + + if d.prefixKeyIdx >= len(d.prefixKeys) { + d.inPrefixMap = false + return "", false, nil + } + key := d.prefixKeys[d.prefixKeyIdx] + d.prefixKeyIdx++ + return key, true, nil +} + +// ReadNil implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadNil(s *smithy.Schema) (bool, error) { + return d.body.ReadNil(s) +} + +// ReadDocument implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadDocument(s *smithy.Schema, v *document.Value) error { + return d.body.ReadDocument(s, v) +} + +// ReadUnion implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadUnion(s *smithy.Schema) (*smithy.Schema, error) { + return d.body.ReadUnion(s) +} + +func (d *ShapeDeserializer) isBindingSet(schema *smithy.Schema) bool { + if name, ok := isHTTPHeader(schema); ok { + return len(d.response.Header.Values(name)) > 0 + } + + if trait, ok := isHTTPPrefixHeaders(schema); ok { + canon := http.CanonicalHeaderKey(trait.Prefix) + for name := range d.response.Header { + if len(name) > len(canon) && strings.EqualFold(name[:len(canon)], canon) { + return true + } + } + return false + } + + if _, ok := smithy.SchemaTrait[*traits.HTTPResponseCode](schema); ok { + return true + } + + if _, ok := smithy.SchemaTrait[*traits.HTTPPayload](schema); ok { + return len(d.payload) > 0 + } + + return false +} + +func (d *ShapeDeserializer) readHeaderString(s *smithy.Schema) (string, error) { + name, _ := isHTTPHeader(s) + + hv := d.response.Header.Get(name) + if _, ok := smithy.SchemaTrait[*traits.MediaType](s); ok { + b, err := base64.StdEncoding.DecodeString(hv) + if err != nil { + return "", err + } + hv = string(b) + } + return hv, nil +} + +func (d *ShapeDeserializer) nextHeaderValue() string { + v := d.headerListValues[d.headerListIdx] + d.headerListIdx++ + return v +} + +type intn interface { + int8 | int16 | int32 | int64 +} + +func readHeaderInt[T intn](d *ShapeDeserializer, s *smithy.Schema, v *T) error { + name, _ := isHTTPHeader(s) + + var hv string + if d.inHeaderList { + hv = d.nextHeaderValue() + } else { + hv = d.response.Header.Get(name) + } + + n, err := strconv.ParseInt(hv, 10, 64) + if err != nil { + return err + } + + *v = T(n) + return nil +} + +type floatn interface { + float32 | float64 +} + +func readHeaderFloat[T floatn](d *ShapeDeserializer, s *smithy.Schema, v *T) error { + name, _ := isHTTPHeader(s) + + var hv string + if d.inHeaderList { + hv = d.nextHeaderValue() + } else { + hv = d.response.Header.Get(name) + } + + n, err := strconv.ParseFloat(hv, 64) + if err != nil { + return err + } + + *v = T(n) + return nil +} + +// ReadBigInt is unimplemented and will return an error. +func (d *ShapeDeserializer) ReadBigInt(_ *smithy.Schema, _ *big.Int) error { + return fmt.Errorf("unimplemented") +} + +// ReadBigFloat is unimplemented and will return an error. +func (d *ShapeDeserializer) ReadBigFloat(_ *smithy.Schema, _ *big.Float) error { + return fmt.Errorf("unimplemented") +} diff --git a/transport/http/protocol/internal/httpbinding/serializer.go b/transport/http/protocol/internal/httpbinding/serializer.go new file mode 100644 index 000000000..b95ede1a0 --- /dev/null +++ b/transport/http/protocol/internal/httpbinding/serializer.go @@ -0,0 +1,633 @@ +package httpbinding + +import ( + "bytes" + "encoding/base64" + "fmt" + "io" + "math/big" + "net/http" + "strconv" + "strings" + "time" + + "github.com/aws/smithy-go" + awsjson "github.com/aws/smithy-go/transport/http/protocol/internal/json" + "github.com/aws/smithy-go/document" + httpbinding "github.com/aws/smithy-go/encoding/httpbinding" + "github.com/aws/smithy-go/traits" + smithyhttp "github.com/aws/smithy-go/transport/http" +) + +// ShapeSerializer routes top-level input struct members to their HTTP binding +// locations. Members without HTTP binding traits delegate to an inner +// serializer for whatever protocol is being used. +// +// ShapeSerializer adds some API surface on top of the normal +// smithy.ShapeSerializer. Specifically it adds [ShapeSerializer.Build] for +// handing REST-protocol payloads, since the actual source of the payload is +// going to vary on a per-operation basis and isn't known until the input's +// Serialize is called. The caller (so, the protocol implementation) should +// set the HTTP request body by calling Build after Serialize. +type ShapeSerializer struct { + request *smithyhttp.Request + encoder *httpbinding.Encoder + input smithy.ShapeSerializer + options ShapeSerializerOptions + + // set when an input member is bound via @httpPayload + httpPayload []byte + httpPayloadContentType string + + // set when an input blob is bound via @httpPayload + @streaming + streamingContentType string + + mapMode mapBindingMode + mapPrefix string + currentKey string + + listMode listBindingMode + listName string + listHasItems bool + + noBody bool + hasStructPayload bool +} + +// ShapeSerializerOptions configures a ShapeSerializer. +type ShapeSerializerOptions struct{} + +// NewShapeSerializer creates a ShapeSerializer for the given operation schema +// and request. It handles the initial setup from use of an +// httpbinding.Encoder. +func NewShapeSerializer(op *smithy.Schema, req *smithyhttp.Request, in smithy.ShapeSerializer, opts ...func(*ShapeSerializerOptions)) (*ShapeSerializer, error) { + httpTrait, ok := smithy.SchemaTrait[*traits.HTTP](op) + if !ok { + return nil, fmt.Errorf("no @http trait on op schema") + } + + req.Method = httpTrait.Method + path, query := httpbinding.SplitURI(httpTrait.URI) + enc, err := httpbinding.NewEncoder(path, query, req.Header) + if err != nil { + return nil, fmt.Errorf("new encoder: %w", err) + } + + return &ShapeSerializer{ + request: req, + encoder: enc, + input: in, + }, nil +} + +// Build encodes HTTP binding values into the request and sets the request +// body. The defaultContentType is used for the protocol body (e.g. +// "application/json") when no explicit payload is present. +// +// The body is resolved in the following priority: +// 1. Streaming payload (input implements StreamingInput with non-nil stream) +// 2. Raw payload bytes (blob/string member with @httpPayload) +// 3. Serialized protocol body (e.g. JSON) +// 4. Empty struct payload (struct member with @httpPayload, sends "{}") +func (s *ShapeSerializer) Build(in smithy.Serializable, defaultContentType string) error { + req := s.request + + built, err := s.encoder.Encode(req.Request) + if err != nil { + return fmt.Errorf("encode httpbinding: %w", err) + } + req.Request = built + + // (1) streaming payload + if si, ok := in.(smithy.StreamingInput); ok && si.GetPayloadStream() != nil { + contentType := s.streamingContentType + if contentType == "" { + contentType = "application/octet-stream" + } + return s.setBody(si.GetPayloadStream(), contentType) + } + + var payload []byte + var contentType string + + // (2) explicit @httpPayload (blob/string) + if s.httpPayload != nil { + payload = s.httpPayload + contentType = s.httpPayloadContentType + } else { // (3) protocol body + payload = s.input.Bytes() + contentType = defaultContentType + } + + // (4) empty struct @httpPayload + if len(payload) == 0 && s.hasStructPayload { + payload = []byte("{}") + contentType = defaultContentType + } + + if len(payload) == 0 { + return nil + } + return s.setBody(bytes.NewReader(payload), contentType) +} + +func (s *ShapeSerializer) setBody(body io.Reader, contentType string) error { + if s.request.Header.Get("Content-Type") == "" { + s.request.Header.Set("Content-Type", contentType) + } + sreq, err := s.request.SetStream(body) + if err != nil { + return fmt.Errorf("set stream: %w", err) + } + *s.request = *sreq + return nil +} + +type mapBindingMode int + +const ( + mapModeNone mapBindingMode = iota + mapModePrefixHeaders + mapModeQueryParams +) + +type listBindingMode int + +const ( + listModeNone listBindingMode = iota + listModeHeader + listModeQuery +) + +type bind int + +const ( + bindBody bind = iota + bindHeader + bindHeaderList + bindQuery + bindQueryList + bindLabel +) + +func (s *ShapeSerializer) resolveBinding(schema *smithy.Schema) (bind, string) { + if s.listMode == listModeHeader { + return bindHeaderList, s.listName + } + if s.listMode == listModeQuery { + return bindQueryList, s.listName + } + if name, ok := isHTTPHeader(schema); ok { + return bindHeader, name + } + if isHTTPLabel(schema) { + return bindLabel, schema.MemberName() + } + if q, ok := isHTTPQuery(schema); ok { + return bindQuery, q.Name + } + return bindBody, "" +} + +var _ smithy.ShapeSerializer = (*ShapeSerializer)(nil) + +// Bytes returns the serialized body bytes. +func (s *ShapeSerializer) Bytes() []byte { + return s.input.Bytes() +} + +func isHTTPHeader(schema *smithy.Schema) (string, bool) { + h, ok := smithy.SchemaTrait[*traits.HTTPHeader](schema) + if !ok { + return "", false + } + return http.CanonicalHeaderKey(h.Name), true +} + +func isHTTPLabel(schema *smithy.Schema) bool { + _, ok := smithy.SchemaTrait[*traits.HTTPLabel](schema) + return ok +} + +func isHTTPQuery(schema *smithy.Schema) (*traits.HTTPQuery, bool) { + return smithy.SchemaTrait[*traits.HTTPQuery](schema) +} + +func isHTTPPayload(schema *smithy.Schema) bool { + _, ok := smithy.SchemaTrait[*traits.HTTPPayload](schema) + return ok +} + +func isHTTPPrefixHeaders(schema *smithy.Schema) (*traits.HTTPPrefixHeaders, bool) { + ph, ok := smithy.SchemaTrait[*traits.HTTPPrefixHeaders](schema) + return ph, ok +} + +func isHTTPQueryParams(schema *smithy.Schema) bool { + _, ok := smithy.SchemaTrait[*traits.HTTPQueryParams](schema) + return ok +} + +func hasBodyMembers(schema *smithy.Schema) bool { + for _, member := range schema.Members() { + if !isHTTPBound(member) { + return true + } + } + return false +} + +func isHTTPBound(schema *smithy.Schema) bool { + if _, ok := isHTTPHeader(schema); ok { + return true + } + if _, ok := isHTTPPrefixHeaders(schema); ok { + return true + } + if isHTTPLabel(schema) { + return true + } + if _, ok := isHTTPQuery(schema); ok { + return true + } + if isHTTPQueryParams(schema) { + return true + } + if isHTTPPayload(schema) { + return true + } + return false +} + +// WriteString implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteString(schema *smithy.Schema, v string) { + switch s.mapMode { + case mapModePrefixHeaders: + s.encoder.SetHeader(http.CanonicalHeaderKey(s.mapPrefix + s.currentKey)).String(v) + return + case mapModeQueryParams: + s.encoder.AddQuery(s.currentKey).String(v) + return + } + + bt, bn := s.resolveBinding(schema) + switch bt { + case bindHeaderList: + escaped := v + if strings.ContainsAny(v, ",\"") { + escaped = strconv.Quote(v) + } + s.encoder.AddHeader(bn).String(escaped) + s.listHasItems = true + case bindQueryList: + s.encoder.AddQuery(bn).String(v) + case bindHeader: + if _, ok := smithy.SchemaTrait[*traits.MediaType](schema); ok { + s.encoder.SetHeader(bn).String(base64.StdEncoding.EncodeToString([]byte(v))) + return + } + s.encoder.SetHeader(bn).String(v) + case bindLabel: + s.encoder.SetURI(bn).String(v) + case bindQuery: + s.encoder.SetQuery(bn).String(v) + default: + if isHTTPPayload(schema) { + s.httpPayload = []byte(v) + s.httpPayloadContentType = "text/plain" + return + } + s.input.WriteString(schema, v) + } +} + +// WriteBool implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteBool(schema *smithy.Schema, v bool) { + bt, bn := s.resolveBinding(schema) + switch bt { + case bindHeaderList: + s.encoder.AddHeader(bn).Boolean(v) + s.listHasItems = true + case bindQueryList: + s.encoder.AddQuery(bn).Boolean(v) + case bindHeader: + s.encoder.SetHeader(bn).Boolean(v) + case bindLabel: + s.encoder.SetURI(bn).Boolean(v) + case bindQuery: + s.encoder.SetQuery(bn).Boolean(v) + default: + s.input.WriteBool(schema, v) + } +} + +// WriteInt8 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt8(schema *smithy.Schema, v int8) { + bt, bn := s.resolveBinding(schema) + switch bt { + case bindHeaderList: + s.encoder.AddHeader(bn).Byte(v) + s.listHasItems = true + case bindQueryList: + s.encoder.AddQuery(bn).Byte(v) + case bindHeader: + s.encoder.SetHeader(bn).Byte(v) + case bindLabel: + s.encoder.SetURI(bn).Byte(v) + case bindQuery: + s.encoder.SetQuery(bn).Byte(v) + default: + s.input.WriteInt8(schema, v) + } +} + +// WriteInt16 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt16(schema *smithy.Schema, v int16) { + bt, bn := s.resolveBinding(schema) + switch bt { + case bindHeaderList: + s.encoder.AddHeader(bn).Short(v) + s.listHasItems = true + case bindQueryList: + s.encoder.AddQuery(bn).Short(v) + case bindHeader: + s.encoder.SetHeader(bn).Short(v) + case bindLabel: + s.encoder.SetURI(bn).Short(v) + case bindQuery: + s.encoder.SetQuery(bn).Short(v) + default: + s.input.WriteInt16(schema, v) + } +} + +// WriteInt32 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt32(schema *smithy.Schema, v int32) { + bt, bn := s.resolveBinding(schema) + switch bt { + case bindHeaderList: + s.encoder.AddHeader(bn).Integer(v) + s.listHasItems = true + case bindQueryList: + s.encoder.AddQuery(bn).Integer(v) + case bindHeader: + s.encoder.SetHeader(bn).Integer(v) + case bindLabel: + s.encoder.SetURI(bn).Integer(v) + case bindQuery: + s.encoder.SetQuery(bn).Integer(v) + default: + s.input.WriteInt32(schema, v) + } +} + +// WriteInt64 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt64(schema *smithy.Schema, v int64) { + bt, bn := s.resolveBinding(schema) + switch bt { + case bindHeaderList: + s.encoder.AddHeader(bn).Long(v) + s.listHasItems = true + case bindQueryList: + s.encoder.AddQuery(bn).Long(v) + case bindHeader: + s.encoder.SetHeader(bn).Long(v) + case bindLabel: + s.encoder.SetURI(bn).Long(v) + case bindQuery: + s.encoder.SetQuery(bn).Long(v) + default: + s.input.WriteInt64(schema, v) + } +} + +// WriteFloat32 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteFloat32(schema *smithy.Schema, v float32) { + bt, bn := s.resolveBinding(schema) + switch bt { + case bindHeaderList: + s.encoder.AddHeader(bn).Float(v) + s.listHasItems = true + case bindQueryList: + s.encoder.AddQuery(bn).Float(v) + case bindHeader: + s.encoder.SetHeader(bn).Float(v) + case bindLabel: + s.encoder.SetURI(bn).Float(v) + case bindQuery: + s.encoder.SetQuery(bn).Float(v) + default: + s.input.WriteFloat32(schema, v) + } +} + +// WriteFloat64 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteFloat64(schema *smithy.Schema, v float64) { + bt, bn := s.resolveBinding(schema) + switch bt { + case bindHeaderList: + s.encoder.AddHeader(bn).Double(v) + s.listHasItems = true + case bindQueryList: + s.encoder.AddQuery(bn).Double(v) + case bindHeader: + s.encoder.SetHeader(bn).Double(v) + case bindLabel: + s.encoder.SetURI(bn).Double(v) + case bindQuery: + s.encoder.SetQuery(bn).Double(v) + default: + s.input.WriteFloat64(schema, v) + } +} + +// WriteBlob implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteBlob(schema *smithy.Schema, v []byte) { + if isHTTPPayload(schema) { + s.httpPayload = v + if mt, ok := smithy.SchemaTrait[*traits.MediaType](schema); ok { + s.httpPayloadContentType = mt.Type + } else { + s.httpPayloadContentType = "application/octet-stream" + } + return + } + if name, ok := isHTTPHeader(schema); ok { + s.encoder.SetHeader(name).Blob(v) + return + } + s.input.WriteBlob(schema, v) +} + +// WriteTime implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteTime(schema *smithy.Schema, v time.Time) { + bt, bn := s.resolveBinding(schema) + switch bt { + case bindHeaderList: + s.encoder.AddHeader(bn).String(formatTimestamp(schema, "http-date", v)) + s.listHasItems = true + case bindQueryList: + s.encoder.AddQuery(bn).String(formatTimestamp(schema, "date-time", v)) + case bindHeader: + s.encoder.SetHeader(bn).String(formatTimestamp(schema, "http-date", v)) + case bindLabel: + s.encoder.SetURI(bn).String(formatTimestamp(schema, "date-time", v)) + case bindQuery: + s.encoder.SetQuery(bn).String(formatTimestamp(schema, "date-time", v)) + default: + s.input.WriteTime(schema, v) + } +} + +// WriteList implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteList(schema *smithy.Schema) { + if s.mapMode == mapModeQueryParams { + s.listMode = listModeQuery + s.listName = s.currentKey + return + } + if name, ok := isHTTPHeader(schema); ok { + s.listMode = listModeHeader + s.listName = name + s.listHasItems = false + return + } + if q, ok := isHTTPQuery(schema); ok { + s.listMode = listModeQuery + s.listName = q.Name + s.listHasItems = false + return + } + s.input.WriteList(schema) +} + +// CloseList implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseList() { + if s.listMode != listModeNone { + if !s.listHasItems && s.listMode == listModeHeader { + s.encoder.SetHeader(s.listName).String("") + } + s.listMode = listModeNone + s.listName = "" + s.listHasItems = false + return + } + s.input.CloseList() +} + +// WriteMap implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteMap(schema *smithy.Schema) { + if ph, ok := isHTTPPrefixHeaders(schema); ok { + s.mapMode = mapModePrefixHeaders + s.mapPrefix = ph.Prefix + return + } + + if isHTTPQueryParams(schema) { + s.mapMode = mapModeQueryParams + return + } + + s.input.WriteMap(schema) +} + +// WriteKey implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteKey(schema *smithy.Schema, key string) { + switch s.mapMode { + case mapModePrefixHeaders, mapModeQueryParams: + s.currentKey = key + default: + s.input.WriteKey(schema, key) + } +} + +// CloseMap implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseMap() { + if s.mapMode != mapModeNone { + s.mapMode = mapModeNone + s.mapPrefix = "" + s.currentKey = "" + return + } + s.input.CloseMap() +} + +// WriteStruct implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteStruct(schema *smithy.Schema) { + if schema.MemberName() != "" { // the root + if isHTTPPayload(schema) { + s.hasStructPayload = true + } + s.input.WriteStruct(schema) + return + } + + for _, m := range schema.Members() { + if !isHTTPPayload(m) { + continue + } + + if _, ok := smithy.SchemaTrait[*traits.Streaming](m); ok { + s.streamingContentType = "application/octet-stream" + if mt, ok := smithy.SchemaTrait[*traits.MediaType](m); ok { + s.streamingContentType = mt.Type + } + } else if m.Type() == smithy.ShapeTypeStructure { + s.hasStructPayload = true + } + } + + if !hasBodyMembers(schema) { + s.noBody = true + return + } + + s.input.WriteStruct(schema) +} + +// CloseStruct implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseStruct() { + if s.noBody { + s.noBody = false + return + } + s.input.CloseStruct() +} + +// WriteUnion implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteUnion(schema, variant *smithy.Schema) { + s.input.WriteUnion(schema, variant) +} + +// CloseUnion implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseUnion() { + s.input.CloseUnion() +} + +// WriteNil implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteNil(schema *smithy.Schema) { + s.input.WriteNil(schema) +} + +// WriteBigInt implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteBigInt(schema *smithy.Schema, v *big.Int) { + s.input.WriteBigInt(schema, v) +} + +// WriteBigFloat implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteBigFloat(schema *smithy.Schema, v *big.Float) { + s.input.WriteBigFloat(schema, v) +} + +// WriteDocument implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteDocument(schema *smithy.Schema, v document.Value) { + if isHTTPPayload(schema) { + // httpPayload document: serialize to raw bytes for the body. + doc := awsjson.NewShapeSerializer() + doc.WriteDocument(schema, v) + s.httpPayload = doc.Bytes() + s.httpPayloadContentType = "application/json" + return + } + s.input.WriteDocument(schema, v) +} diff --git a/transport/http/protocol/internal/httpbinding/timestamp.go b/transport/http/protocol/internal/httpbinding/timestamp.go new file mode 100644 index 000000000..335ac6c52 --- /dev/null +++ b/transport/http/protocol/internal/httpbinding/timestamp.go @@ -0,0 +1,44 @@ +package httpbinding + +import ( + "fmt" + "strconv" + "time" + + "github.com/aws/smithy-go" + smithytime "github.com/aws/smithy-go/time" + "github.com/aws/smithy-go/traits" +) + +func timestampFormat(schema *smithy.Schema, fallback string) string { + if tf, ok := smithy.SchemaTrait[*traits.TimestampFormat](schema); ok { + return tf.Format + } + return fallback +} + +func formatTimestamp(schema *smithy.Schema, fallback string, v time.Time) string { + switch timestampFormat(schema, fallback) { + case "http-date": + return smithytime.FormatHTTPDate(v) + case "date-time": + return smithytime.FormatDateTime(v) + default: // "epoch-seconds" + return strconv.FormatFloat(smithytime.FormatEpochSeconds(v), 'f', -1, 64) + } +} + +func parseTimestamp(schema *smithy.Schema, fallback, s string) (time.Time, error) { + switch timestampFormat(schema, fallback) { + case "http-date": + return smithytime.ParseHTTPDate(s) + case "date-time": + return smithytime.ParseDateTime(s) + default: // "epoch-seconds" + v, err := strconv.ParseFloat(s, 64) + if err != nil { + return time.Time{}, fmt.Errorf("parse epoch-seconds %q: %w", s, err) + } + return smithytime.ParseEpochSeconds(v), nil + } +} diff --git a/transport/http/protocol/internal/json/benchmark_test.go b/transport/http/protocol/internal/json/benchmark_test.go new file mode 100644 index 000000000..6fd9f6637 --- /dev/null +++ b/transport/http/protocol/internal/json/benchmark_test.go @@ -0,0 +1,819 @@ +package json + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "testing" + + "github.com/aws/smithy-go" + "github.com/aws/smithy-go/prelude" +) + +// Benchmark comparing old (stdlib json.Decoder + tree walk) vs new (fastjson +// ShapeDeserializer) for DynamoDB AttributeValue deserialization. +// +// The payload is a GetItem response with a realistic mix of attribute types. + +// --- test payload --- + +var benchPayload = []byte(`{ + "ConsumedCapacity": { + "TableName": "Users", + "CapacityUnits": 0.5 + }, + "Item": { + "pk": {"S": "user#12345"}, + "sk": {"S": "profile"}, + "name": {"S": "Jane Doe"}, + "age": {"N": "34"}, + "verified": {"BOOL": true}, + "email": {"S": "jane@example.com"}, + "scores": {"NS": ["98", "76", "100", "88"]}, + "tags": {"SS": ["admin", "premium", "early-adopter"]}, + "avatar": {"B": "` + base64.StdEncoding.EncodeToString([]byte("fake-image-bytes-here")) + `"}, + "metadata": {"M": { + "created": {"S": "2024-01-15T10:30:00Z"}, + "source": {"S": "web"}, + "loginCount": {"N": "142"}, + "preferences": {"M": { + "theme": {"S": "dark"}, + "notifications": {"BOOL": true} + }} + }}, + "history": {"L": [ + {"M": {"action": {"S": "login"}, "ts": {"N": "1700000000"}}}, + {"M": {"action": {"S": "purchase"}, "ts": {"N": "1700100000"}}}, + {"M": {"action": {"S": "logout"}, "ts": {"N": "1700200000"}}} + ]}, + "nothing": {"NULL": true} + } +}`) + +// --- types (mirrors DynamoDB SDK types) --- + +type AttributeValue interface{ isAttributeValue() } + +type AttributeValueMemberS struct{ Value string } +type AttributeValueMemberN struct{ Value string } +type AttributeValueMemberB struct{ Value []byte } +type AttributeValueMemberBOOL struct{ Value bool } +type AttributeValueMemberNULL struct{ Value bool } +type AttributeValueMemberSS struct{ Value []string } +type AttributeValueMemberNS struct{ Value []string } +type AttributeValueMemberBS struct{ Value [][]byte } +type AttributeValueMemberL struct{ Value []AttributeValue } +type AttributeValueMemberM struct{ Value map[string]AttributeValue } + +func (*AttributeValueMemberS) isAttributeValue() {} +func (*AttributeValueMemberN) isAttributeValue() {} +func (*AttributeValueMemberB) isAttributeValue() {} +func (*AttributeValueMemberBOOL) isAttributeValue() {} +func (*AttributeValueMemberNULL) isAttributeValue() {} +func (*AttributeValueMemberSS) isAttributeValue() {} +func (*AttributeValueMemberNS) isAttributeValue() {} +func (*AttributeValueMemberBS) isAttributeValue() {} +func (*AttributeValueMemberL) isAttributeValue() {} +func (*AttributeValueMemberM) isAttributeValue() {} + +type ConsumedCapacity struct { + TableName *string + CapacityUnits float64 +} + +type GetItemOutput struct { + ConsumedCapacity *ConsumedCapacity + Item map[string]AttributeValue +} + +// ========================================================================== +// OLD PATH: json.Decoder -> interface{} -> tree walk +// ========================================================================== + +func oldDeserialize(data []byte) (*GetItemOutput, error) { + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.UseNumber() + var shape interface{} + if err := decoder.Decode(&shape); err != nil && err != io.EOF { + return nil, err + } + + var out GetItemOutput + if err := oldDeserializeGetItemOutput(&out, shape); err != nil { + return nil, err + } + return &out, nil +} + +func oldDeserializeGetItemOutput(v *GetItemOutput, value interface{}) error { + if value == nil { + return nil + } + + shape, ok := value.(map[string]interface{}) + if !ok { + return fmt.Errorf("unexpected JSON type %v", value) + } + + for key, value := range shape { + switch key { + case "ConsumedCapacity": + if err := oldDeserializeConsumedCapacity(&v.ConsumedCapacity, value); err != nil { + return err + } + case "Item": + if err := oldDeserializeAttributeMap(&v.Item, value); err != nil { + return err + } + } + } + return nil +} + +func oldDeserializeConsumedCapacity(v **ConsumedCapacity, value interface{}) error { + if value == nil { + return nil + } + + shape, ok := value.(map[string]interface{}) + if !ok { + return fmt.Errorf("unexpected JSON type %v", value) + } + + sv := &ConsumedCapacity{} + for key, value := range shape { + switch key { + case "TableName": + if value != nil { + jtv, ok := value.(string) + if !ok { + return fmt.Errorf("expected string, got %T", value) + } + sv.TableName = &jtv + } + case "CapacityUnits": + if value != nil { + jtv, ok := value.(json.Number) + if !ok { + return fmt.Errorf("expected number, got %T", value) + } + f, err := jtv.Float64() + if err != nil { + return err + } + sv.CapacityUnits = f + } + } + } + *v = sv + return nil +} + +func oldDeserializeAttributeMap(v *map[string]AttributeValue, value interface{}) error { + if value == nil { + return nil + } + + shape, ok := value.(map[string]interface{}) + if !ok { + return fmt.Errorf("unexpected JSON type %v", value) + } + + mv := make(map[string]AttributeValue, len(shape)) + for key, value := range shape { + var parsedVal AttributeValue + if err := oldDeserializeAttributeValue(&parsedVal, value); err != nil { + return err + } + mv[key] = parsedVal + } + *v = mv + return nil +} + +func oldDeserializeAttributeValue(v *AttributeValue, value interface{}) error { + if value == nil { + return nil + } + + shape, ok := value.(map[string]interface{}) + if !ok { + return fmt.Errorf("unexpected JSON type %v", value) + } + + for key, value := range shape { + if value == nil { + continue + } + switch key { + case "S": + jtv, ok := value.(string) + if !ok { + return fmt.Errorf("expected string, got %T", value) + } + *v = &AttributeValueMemberS{Value: jtv} + return nil + case "N": + jtv, ok := value.(string) + if !ok { + return fmt.Errorf("expected string, got %T", value) + } + *v = &AttributeValueMemberN{Value: jtv} + return nil + case "B": + jtv, ok := value.(string) + if !ok { + return fmt.Errorf("expected string, got %T", value) + } + dv, err := base64.StdEncoding.DecodeString(jtv) + if err != nil { + return err + } + *v = &AttributeValueMemberB{Value: dv} + return nil + case "BOOL": + jtv, ok := value.(bool) + if !ok { + return fmt.Errorf("expected bool, got %T", value) + } + *v = &AttributeValueMemberBOOL{Value: jtv} + return nil + case "NULL": + jtv, ok := value.(bool) + if !ok { + return fmt.Errorf("expected bool, got %T", value) + } + *v = &AttributeValueMemberNULL{Value: jtv} + return nil + case "SS": + if err := oldDeserializeStringSet(v, value); err != nil { + return err + } + return nil + case "NS": + if err := oldDeserializeNumberSet(v, value); err != nil { + return err + } + return nil + case "BS": + if err := oldDeserializeBinarySet(v, value); err != nil { + return err + } + return nil + case "L": + if err := oldDeserializeList(v, value); err != nil { + return err + } + return nil + case "M": + if err := oldDeserializeMap(v, value); err != nil { + return err + } + return nil + } + } + return nil +} + +func oldDeserializeStringSet(v *AttributeValue, value interface{}) error { + shape, ok := value.([]interface{}) + if !ok { + return fmt.Errorf("expected list, got %T", value) + } + + cv := make([]string, 0, len(shape)) + for _, value := range shape { + jtv, ok := value.(string) + if !ok { + return fmt.Errorf("expected string, got %T", value) + } + cv = append(cv, jtv) + } + *v = &AttributeValueMemberSS{Value: cv} + return nil +} + +func oldDeserializeNumberSet(v *AttributeValue, value interface{}) error { + shape, ok := value.([]interface{}) + if !ok { + return fmt.Errorf("expected list, got %T", value) + } + + cv := make([]string, 0, len(shape)) + for _, value := range shape { + jtv, ok := value.(string) + if !ok { + return fmt.Errorf("expected string, got %T", value) + } + cv = append(cv, jtv) + } + *v = &AttributeValueMemberNS{Value: cv} + return nil +} + +func oldDeserializeBinarySet(v *AttributeValue, value interface{}) error { + shape, ok := value.([]interface{}) + if !ok { + return fmt.Errorf("expected list, got %T", value) + } + + cv := make([][]byte, 0, len(shape)) + for _, value := range shape { + jtv, ok := value.(string) + if !ok { + return fmt.Errorf("expected string, got %T", value) + } + dv, err := base64.StdEncoding.DecodeString(jtv) + if err != nil { + return err + } + cv = append(cv, dv) + } + *v = &AttributeValueMemberBS{Value: cv} + return nil +} + +func oldDeserializeList(v *AttributeValue, value interface{}) error { + shape, ok := value.([]interface{}) + if !ok { + return fmt.Errorf("expected list, got %T", value) + } + + cv := make([]AttributeValue, 0, len(shape)) + for _, value := range shape { + var col AttributeValue + if err := oldDeserializeAttributeValue(&col, value); err != nil { + return err + } + cv = append(cv, col) + } + *v = &AttributeValueMemberL{Value: cv} + return nil +} + +func oldDeserializeMap(v *AttributeValue, value interface{}) error { + shape, ok := value.(map[string]interface{}) + if !ok { + return fmt.Errorf("expected map, got %T", value) + } + + mv := make(map[string]AttributeValue, len(shape)) + for key, value := range shape { + var parsedVal AttributeValue + if err := oldDeserializeAttributeValue(&parsedVal, value); err != nil { + return err + } + mv[key] = parsedVal + } + *v = &AttributeValueMemberM{Value: mv} + return nil +} + +// ========================================================================== +// NEW PATH: fastjson ShapeDeserializer + schemas +// ========================================================================== + +// --- schemas --- + +var ( + schemaGetItemOutput = smithy.NewSchema(smithy.ShapeID{ + Namespace: "com.amazonaws.dynamodb", Name: "GetItemOutput", + }, smithy.ShapeTypeStructure, 2) + + schemaConsumedCapacity = smithy.NewSchema(smithy.ShapeID{ + Namespace: "com.amazonaws.dynamodb", Name: "ConsumedCapacity", + }, smithy.ShapeTypeStructure, 2) + + schemaAttributeValue = smithy.NewSchema(smithy.ShapeID{ + Namespace: "com.amazonaws.dynamodb", Name: "AttributeValue", + }, smithy.ShapeTypeUnion, 10) + + schemaAttributeMap = smithy.NewSchema(smithy.ShapeID{ + Namespace: "com.amazonaws.dynamodb", Name: "AttributeMap", + }, smithy.ShapeTypeMap, 0) + + schemaListAttributeValue = smithy.NewSchema(smithy.ShapeID{ + Namespace: "com.amazonaws.dynamodb", Name: "ListAttributeValue", + }, smithy.ShapeTypeList, 0) + + schemaMapAttributeValue = smithy.NewSchema(smithy.ShapeID{ + Namespace: "com.amazonaws.dynamodb", Name: "MapAttributeValue", + }, smithy.ShapeTypeMap, 0) + + schemaStringSetAttributeValue = smithy.NewSchema(smithy.ShapeID{ + Namespace: "com.amazonaws.dynamodb", Name: "StringSetAttributeValue", + }, smithy.ShapeTypeList, 0) + + schemaNumberSetAttributeValue = smithy.NewSchema(smithy.ShapeID{ + Namespace: "com.amazonaws.dynamodb", Name: "NumberSetAttributeValue", + }, smithy.ShapeTypeList, 0) + + schemaBinarySetAttributeValue = smithy.NewSchema(smithy.ShapeID{ + Namespace: "com.amazonaws.dynamodb", Name: "BinarySetAttributeValue", + }, smithy.ShapeTypeList, 0) + + // member schemas (populated in init) + schemaGetItemOutput_ConsumedCapacity *smithy.Schema + schemaGetItemOutput_Item *smithy.Schema + schemaConsumedCapacity_TableName *smithy.Schema + schemaConsumedCapacity_CapacityUnits *smithy.Schema + schemaAttributeValue_S *smithy.Schema + schemaAttributeValue_N *smithy.Schema + schemaAttributeValue_B *smithy.Schema + schemaAttributeValue_BOOL *smithy.Schema + schemaAttributeValue_NULL *smithy.Schema + schemaAttributeValue_SS *smithy.Schema + schemaAttributeValue_NS *smithy.Schema + schemaAttributeValue_BS *smithy.Schema + schemaAttributeValue_L *smithy.Schema + schemaAttributeValue_M *smithy.Schema +) + +func init() { + schemaGetItemOutput_ConsumedCapacity = schemaGetItemOutput.AddMember("ConsumedCapacity", schemaConsumedCapacity) + schemaGetItemOutput_Item = schemaGetItemOutput.AddMember("Item", schemaAttributeMap) + + schemaConsumedCapacity_TableName = schemaConsumedCapacity.AddMember("TableName", prelude.String) + schemaConsumedCapacity_CapacityUnits = schemaConsumedCapacity.AddMember("CapacityUnits", prelude.Double) + + schemaAttributeValue_S = schemaAttributeValue.AddMember("S", prelude.String) + schemaAttributeValue_N = schemaAttributeValue.AddMember("N", prelude.String) + schemaAttributeValue_B = schemaAttributeValue.AddMember("B", prelude.Blob) + schemaAttributeValue_BOOL = schemaAttributeValue.AddMember("BOOL", prelude.Boolean) + schemaAttributeValue_NULL = schemaAttributeValue.AddMember("NULL", prelude.Boolean) + schemaAttributeValue_SS = schemaAttributeValue.AddMember("SS", schemaStringSetAttributeValue) + schemaAttributeValue_NS = schemaAttributeValue.AddMember("NS", schemaNumberSetAttributeValue) + schemaAttributeValue_BS = schemaAttributeValue.AddMember("BS", schemaBinarySetAttributeValue) + schemaAttributeValue_L = schemaAttributeValue.AddMember("L", schemaListAttributeValue) + schemaAttributeValue_M = schemaAttributeValue.AddMember("M", schemaMapAttributeValue) + + schemaAttributeMap.AddMember("key", prelude.String) + schemaAttributeMap.AddMember("value", schemaAttributeValue) + + schemaListAttributeValue.AddMember("member", schemaAttributeValue) + + schemaMapAttributeValue.AddMember("key", prelude.String) + schemaMapAttributeValue.AddMember("value", schemaAttributeValue) + + schemaStringSetAttributeValue.AddMember("member", prelude.String) + schemaNumberSetAttributeValue.AddMember("member", prelude.String) + schemaBinarySetAttributeValue.AddMember("member", prelude.Blob) +} + +// --- new deserializers --- + +func newDeserialize(data []byte) (*GetItemOutput, error) { + d := NewShapeDeserializer(data) + defer d.Close() + var out GetItemOutput + if err := newDeserializeGetItemOutput(d, &out); err != nil { + return nil, err + } + return &out, nil +} + +func newDeserializeGetItemOutput(d *ShapeDeserializer, v *GetItemOutput) error { + return d.DirectReadStruct(schemaGetItemOutput, func(ms *smithy.Schema) error { + switch ms { + case schemaGetItemOutput_ConsumedCapacity: + v.ConsumedCapacity = &ConsumedCapacity{} + return newDeserializeConsumedCapacity(d, v.ConsumedCapacity) + case schemaGetItemOutput_Item: + return newDeserializeAttributeMap(d, &v.Item) + } + return nil + }) +} + +func newDeserializeConsumedCapacity(d *ShapeDeserializer, v *ConsumedCapacity) error { + return d.DirectReadStruct(schemaConsumedCapacity, func(ms *smithy.Schema) error { + switch ms { + case schemaConsumedCapacity_TableName: + v.TableName = new(string) + return d.ReadString(schemaConsumedCapacity_TableName, v.TableName) + case schemaConsumedCapacity_CapacityUnits: + return d.ReadFloat64(schemaConsumedCapacity_CapacityUnits, &v.CapacityUnits) + } + return nil + }) +} + +func newDeserializeAttributeMap(d *ShapeDeserializer, v *map[string]AttributeValue) error { + return d.DirectReadMap(schemaAttributeMap, func(key string) error { + var av AttributeValue + if err := newDeserializeAttributeValue(d, &av); err != nil { + return err + } + if *v == nil { + *v = make(map[string]AttributeValue, 8) + } + (*v)[key] = av + return nil + }) +} + +func newDeserializeAttributeValue(d *ShapeDeserializer, v *AttributeValue) error { + return d.DirectReadUnion(schemaAttributeValue, func(ms *smithy.Schema) error { + switch ms { + case schemaAttributeValue_S: + vv := &AttributeValueMemberS{} + *v = vv + return d.ReadString(schemaAttributeValue_S, &vv.Value) + case schemaAttributeValue_N: + vv := &AttributeValueMemberN{} + *v = vv + return d.ReadString(schemaAttributeValue_N, &vv.Value) + case schemaAttributeValue_B: + vv := &AttributeValueMemberB{} + *v = vv + return d.ReadBlob(schemaAttributeValue_B, &vv.Value) + case schemaAttributeValue_BOOL: + vv := &AttributeValueMemberBOOL{} + *v = vv + return d.ReadBool(schemaAttributeValue_BOOL, &vv.Value) + case schemaAttributeValue_NULL: + vv := &AttributeValueMemberNULL{} + *v = vv + return d.ReadBool(schemaAttributeValue_NULL, &vv.Value) + case schemaAttributeValue_SS: + vv := &AttributeValueMemberSS{} + *v = vv + return d.DirectReadList(schemaStringSetAttributeValue, func() error { + var s string + if err := d.ReadString(nil, &s); err != nil { + return err + } + vv.Value = append(vv.Value, s) + return nil + }) + case schemaAttributeValue_NS: + vv := &AttributeValueMemberNS{} + *v = vv + return d.DirectReadList(schemaNumberSetAttributeValue, func() error { + var s string + if err := d.ReadString(nil, &s); err != nil { + return err + } + vv.Value = append(vv.Value, s) + return nil + }) + case schemaAttributeValue_BS: + vv := &AttributeValueMemberBS{} + *v = vv + return d.DirectReadList(schemaBinarySetAttributeValue, func() error { + var b []byte + if err := d.ReadBlob(nil, &b); err != nil { + return err + } + vv.Value = append(vv.Value, b) + return nil + }) + case schemaAttributeValue_L: + vv := &AttributeValueMemberL{} + *v = vv + return d.DirectReadList(schemaListAttributeValue, func() error { + var av AttributeValue + if err := newDeserializeAttributeValue(d, &av); err != nil { + return err + } + vv.Value = append(vv.Value, av) + return nil + }) + case schemaAttributeValue_M: + vv := &AttributeValueMemberM{} + *v = vv + return d.DirectReadMap(schemaMapAttributeValue, func(key string) error { + var av AttributeValue + if err := newDeserializeAttributeValue(d, &av); err != nil { + return err + } + if vv.Value == nil { + vv.Value = map[string]AttributeValue{} + } + vv.Value[key] = av + return nil + }) + } + return nil + }) +} + +// ========================================================================== +// Benchmarks +// ========================================================================== + +func BenchmarkDeserialize_Old(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + out, err := oldDeserialize(benchPayload) + if err != nil { + b.Fatal(err) + } + if out.Item == nil { + b.Fatal("nil item") + } + } +} + +func BenchmarkDeserialize_New(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + out, err := newDeserialize(benchPayload) + if err != nil { + b.Fatal(err) + } + if out.Item == nil { + b.Fatal("nil item") + } + } +} + +// ========================================================================== +// Serialize benchmarks +// ========================================================================== + +func newSerialize(out *GetItemOutput) ([]byte, error) { + s := NewShapeSerializer() + newSerializeGetItemOutput(s, out) + b := s.Bytes() + s.Close() + return b, nil +} + +func newSerializeGetItemOutput(s *ShapeSerializer, v *GetItemOutput) { + s.WriteStruct(schemaGetItemOutput) + if v.ConsumedCapacity != nil { + newSerializeConsumedCapacity(s, schemaGetItemOutput_ConsumedCapacity, v.ConsumedCapacity) + } + if v.Item != nil { + newSerializeAttributeMap(s, schemaGetItemOutput_Item, v.Item) + } + s.CloseStruct() +} + +func newSerializeConsumedCapacity(s *ShapeSerializer, schema *smithy.Schema, v *ConsumedCapacity) { + s.WriteStruct(schema) + if v.TableName != nil { + s.WriteString(schemaConsumedCapacity_TableName, *v.TableName) + } + s.WriteFloat64(schemaConsumedCapacity_CapacityUnits, v.CapacityUnits) + s.CloseStruct() +} + +func newSerializeAttributeMap(s *ShapeSerializer, schema *smithy.Schema, m map[string]AttributeValue) { + s.WriteMap(schema) + for k, v := range m { + s.WriteKey(nil, k) + newSerializeAttributeValue(s, v) + } + s.CloseMap() +} + +func newSerializeAttributeValue(s *ShapeSerializer, v AttributeValue) { + switch av := v.(type) { + case *AttributeValueMemberS: + s.WriteUnion(schemaAttributeValue, schemaAttributeValue_S) + s.WriteString(schemaAttributeValue_S, av.Value) + s.CloseUnion() + case *AttributeValueMemberN: + s.WriteUnion(schemaAttributeValue, schemaAttributeValue_N) + s.WriteString(schemaAttributeValue_N, av.Value) + s.CloseUnion() + case *AttributeValueMemberB: + s.WriteUnion(schemaAttributeValue, schemaAttributeValue_B) + s.WriteBlob(schemaAttributeValue_B, av.Value) + s.CloseUnion() + case *AttributeValueMemberBOOL: + s.WriteUnion(schemaAttributeValue, schemaAttributeValue_BOOL) + s.WriteBool(schemaAttributeValue_BOOL, av.Value) + s.CloseUnion() + case *AttributeValueMemberNULL: + s.WriteUnion(schemaAttributeValue, schemaAttributeValue_NULL) + s.WriteBool(schemaAttributeValue_NULL, av.Value) + s.CloseUnion() + case *AttributeValueMemberSS: + s.WriteUnion(schemaAttributeValue, schemaAttributeValue_SS) + s.WriteList(schemaAttributeValue_SS) + for _, item := range av.Value { + s.WriteString(nil, item) + } + s.CloseList() + s.CloseUnion() + case *AttributeValueMemberNS: + s.WriteUnion(schemaAttributeValue, schemaAttributeValue_NS) + s.WriteList(schemaAttributeValue_NS) + for _, item := range av.Value { + s.WriteString(nil, item) + } + s.CloseList() + s.CloseUnion() + case *AttributeValueMemberBS: + s.WriteUnion(schemaAttributeValue, schemaAttributeValue_BS) + s.WriteList(schemaAttributeValue_BS) + for _, item := range av.Value { + s.WriteBlob(nil, item) + } + s.CloseList() + s.CloseUnion() + case *AttributeValueMemberL: + s.WriteUnion(schemaAttributeValue, schemaAttributeValue_L) + s.WriteList(schemaAttributeValue_L) + for _, item := range av.Value { + newSerializeAttributeValue(s, item) + } + s.CloseList() + s.CloseUnion() + case *AttributeValueMemberM: + s.WriteUnion(schemaAttributeValue, schemaAttributeValue_M) + s.WriteMap(schemaAttributeValue_M) + for k, item := range av.Value { + s.WriteKey(nil, k) + newSerializeAttributeValue(s, item) + } + s.CloseMap() + s.CloseUnion() + } +} + +var benchOutput *GetItemOutput + +func init() { + var err error + benchOutput, err = newDeserialize(benchPayload) + if err != nil { + panic(err) + } +} + +func BenchmarkSerialize_New(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + out, err := newSerialize(benchOutput) + if err != nil { + b.Fatal(err) + } + if len(out) == 0 { + b.Fatal("empty") + } + } +} + +// ========================================================================== +// Large payload +// ========================================================================== + +func generateLargePayload(targetSize int) []byte { + var buf bytes.Buffer + buf.WriteString(`{"ConsumedCapacity":{"TableName":"Bench","CapacityUnits":1.0},"Item":{`) + i := 0 + for buf.Len() < targetSize { + if i > 0 { + buf.WriteByte(',') + } + key := fmt.Sprintf("attr_%d", i) + switch i % 7 { + case 0: + fmt.Fprintf(&buf, `"%s":{"S":"value-%d-padding-to-add-some-length-here"}`, key, i) + case 1: + fmt.Fprintf(&buf, `"%s":{"N":"%d"}`, key, i*100) + case 2: + fmt.Fprintf(&buf, `"%s":{"BOOL":%t}`, key, i%2 == 0) + case 3: + fmt.Fprintf(&buf, `"%s":{"SS":["alpha%d","bravo%d","charlie%d","delta%d"]}`, key, i, i, i, i) + case 4: + fmt.Fprintf(&buf, `"%s":{"NS":["%d","%d","%d","%d"]}`, key, i, i+1, i+2, i+3) + case 5: + fmt.Fprintf(&buf, `"%s":{"M":{"nested":{"S":"v%d"},"count":{"N":"%d"},"flag":{"BOOL":true}}}`, key, i, i) + case 6: + fmt.Fprintf(&buf, `"%s":{"L":[{"S":"item%d"},{"N":"%d"},{"BOOL":true},{"NULL":true}]}`, key, i, i) + } + i++ + } + buf.WriteString(`}}`) + return buf.Bytes() +} + +var largePayload = generateLargePayload(512 * 1024 * 1024) + +func BenchmarkLargePayload_Old(b *testing.B) { + b.SetBytes(int64(len(largePayload))) + b.ReportAllocs() + for b.Loop() { + if _, err := oldDeserialize(largePayload); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkLargePayload_New(b *testing.B) { + b.SetBytes(int64(len(largePayload))) + b.ReportAllocs() + for b.Loop() { + if _, err := newDeserialize(largePayload); err != nil { + b.Fatal(err) + } + } +} + diff --git a/transport/http/protocol/internal/json/error.go b/transport/http/protocol/internal/json/error.go new file mode 100644 index 000000000..49a530dcc --- /dev/null +++ b/transport/http/protocol/internal/json/error.go @@ -0,0 +1,54 @@ +package json + +import ( + "encoding/json" + "io" + "strings" +) + +// ProtocolErrorInfo holds the error type and message decoded from a JSON +// error response body. +type ProtocolErrorInfo struct { + Type string `json:"__type"` + Message string + + // nonstandard, but some AWS services do present the type here + Code any +} + +// GetProtocolErrorInfo decodes error type/message from a JSON response body. +func GetProtocolErrorInfo(decoder *json.Decoder) (ProtocolErrorInfo, error) { + var errInfo ProtocolErrorInfo + if err := decoder.Decode(&errInfo); err != nil { + if err == io.EOF { + return errInfo, nil + } + return errInfo, err + } + return errInfo, nil +} + +// ResolveProtocolErrorType resolves the error type from the header value and +// body info, returning the type and whether one was found. +func ResolveProtocolErrorType(headerType string, bodyInfo ProtocolErrorInfo) (string, bool) { + if len(headerType) != 0 { + return headerType, true + } else if len(bodyInfo.Type) != 0 { + return bodyInfo.Type, true + } else if code, ok := bodyInfo.Code.(string); ok && len(code) != 0 { + return code, true + } + return "", false +} + +// SanitizeErrorCode strips namespace prefixes and URI suffixes from error +// codes received on the wire. +func SanitizeErrorCode(code string) string { + if idx := strings.Index(code, ":"); idx != -1 { + code = code[:idx] + } + if idx := strings.Index(code, "#"); idx != -1 { + code = code[idx+1:] + } + return code +} diff --git a/transport/http/protocol/internal/json/ext.go b/transport/http/protocol/internal/json/ext.go new file mode 100644 index 000000000..82b74aed3 --- /dev/null +++ b/transport/http/protocol/internal/json/ext.go @@ -0,0 +1,117 @@ +package json + +import ( + "sort" + + "github.com/aws/smithy-go" + "github.com/aws/smithy-go/traits" +) + +type jsonExt struct { + jsonKey []byte // `,"memberName":` -- use [1:] when no comma needed + jsonNameKey []byte // `,"jsonName":` -- use [1:] when no comma needed (nil if no @jsonName) + + memberList []memberEntry + byteIndex *[256]int16 +} + +type memberEntry struct { + name string + schema *smithy.Schema +} + +func getExt(s *smithy.Schema) *jsonExt { + return smithy.SchemaExtension(s, smithy.ExtJSON, buildJSONExt) +} + +func buildJSONExt(s *smithy.Schema) *jsonExt { + ext := &jsonExt{} + + if name := s.MemberName(); name != "" { + ext.jsonKey = encodeJSONKey(name) + if jn, ok := smithy.SchemaTrait[*traits.JSONName](s); ok { + ext.jsonNameKey = encodeJSONKey(jn.Name) + } + } + + if members := s.Members(); len(members) > 0 { + names := make([]string, 0, len(members)) + for name := range members { + names = append(names, name) + } + sort.Strings(names) + + ext.memberList = make([]memberEntry, len(names)) + idx := &[256]int16{} + for i := range idx { + idx[i] = -1 + } + for pos, name := range names { + ext.memberList[pos] = memberEntry{name: name, schema: members[name]} + if len(name) > 0 { + b := name[0] + if idx[b] == -1 { + idx[b] = int16(pos) + } else { + idx[b] = -2 + } + } + } + ext.byteIndex = idx + } + + return ext +} + +func memberByBytes(s *smithy.Schema, name []byte) *smithy.Schema { + ext := getExt(s) + if ext.byteIndex == nil || len(name) == 0 { + return nil + } + idx := ext.byteIndex[name[0]] + if idx == -1 { + return nil + } + if idx >= 0 { + e := &ext.memberList[idx] + if len(e.name) == len(name) && e.name == string(name) { + return e.schema + } + return nil + } + for i := range ext.memberList { + e := &ext.memberList[i] + if len(e.name) == len(name) && e.name == string(name) { + return e.schema + } + } + return nil +} + +func encodeJSONKey(name string) []byte { + buf := make([]byte, 0, len(name)+4) + buf = append(buf, ',', '"') + for i := 0; i < len(name); i++ { + c := name[i] + switch c { + case '"': + buf = append(buf, '\\', '"') + case '\\': + buf = append(buf, '\\', '\\') + case '\n': + buf = append(buf, '\\', 'n') + case '\r': + buf = append(buf, '\\', 'r') + case '\t': + buf = append(buf, '\\', 't') + default: + if c < 0x20 { + buf = append(buf, '\\', 'u', '0', '0', "0123456789abcdef"[c>>4], "0123456789abcdef"[c&0xF]) + } else { + buf = append(buf, c) + } + } + } + buf = append(buf, '"', ':') + return buf +} diff --git a/transport/http/protocol/internal/json/fuzz_test.go b/transport/http/protocol/internal/json/fuzz_test.go new file mode 100644 index 000000000..fdba6c34d --- /dev/null +++ b/transport/http/protocol/internal/json/fuzz_test.go @@ -0,0 +1,327 @@ +package json + +import ( + "encoding/binary" + "encoding/json" + "fmt" + "io" + "math" + "reflect" + "testing" + "unicode/utf8" +) + +// FuzzParser checks that the parser doesn't crash on arbitrary input. +func FuzzParser(f *testing.F) { + for _, seed := range fuzzSeeds { + f.Add(seed) + } + + f.Fuzz(func(t *testing.T, b []byte) { + p := parser{ + p: b, + state: stValue, + } + for { + _, err := p.Next() + if err != nil { + if err == io.EOF { + break + } + return + } + } + }) +} + +// FuzzParserDifferential compares our parser's accept/reject and parsed values +// against encoding/json. Any input accepted by one but rejected by the other +// (or producing different values) is a bug. +func FuzzParserDifferential(f *testing.F) { + for _, seed := range fuzzSeeds { + f.Add(seed) + } + + f.Fuzz(func(t *testing.T, b []byte) { + var stdVal any + stdErr := json.Unmarshal(b, &stdVal) + + p := parser{ + p: b, + state: stValue, + } + ourVal, ourErr := p.Value() + if ourErr == nil { + // parser.Value() reads one value from a stream and stops. It does + // not reject trailing content, but json.Unmarshal does. Check + // explicitly so the comparison is apples-to-apples. + for i := p.i; i < len(p.p); i++ { + if p.p[i] != ' ' && p.p[i] != '\t' && p.p[i] != '\n' && p.p[i] != '\r' { + ourErr = fmt.Errorf("trailing content at offset %d", i) + break + } + } + } + + stdOK := stdErr == nil + ourOK := ourErr == nil + + if stdOK != ourOK { + t.Errorf("accept/reject mismatch\ninput: %q\nstdlib ok: %v (err: %v)\nours ok: %v (err: %v)\nour value: %v", + b, stdOK, stdErr, ourOK, ourErr, ourVal) + return + } + + if !stdOK { + return + } + + if !reflect.DeepEqual(normalize(stdVal), normalize(ourVal)) { + t.Errorf("value mismatch\ninput: %q\nstdlib: %v\nours: %v", b, stdVal, ourVal) + } + }) +} + +// normalize recursively walks a parsed value tree. Both parsers produce the +// same types (float64 for numbers, string, bool, nil, []any, map[string]any) +// so this is mainly defensive. +func normalize(v any) any { + switch vv := v.(type) { + case map[string]any: + for k, val := range vv { + vv[k] = normalize(val) + } + return vv + case []any: + for i, val := range vv { + vv[i] = normalize(val) + } + return vv + default: + return v + } +} + +var fuzzSeeds = [][]byte{ + []byte(`{ +"object": { + "slice": [ + 1, + 2.0, + "3", + [4], + {5: {}} + ] +}, +"slice": [[]], +"string": ":)", +"int": 1e5, +"float": 3e-9" +}`), + []byte(`null`), + []byte(`true`), + []byte(`false`), + []byte(`0`), + []byte(`""`), + []byte(`"\u0000"`), + []byte(`"\uD834\uDD1E"`), + []byte(`{}`), + []byte(`[]`), + []byte(`{"a":1,"b":[2,3],"c":{"d":true}}`), + []byte(`[[[[[[[[[[[[[[[1]]]]]]]]]]]]]]]`), + []byte(`"\/"`), + []byte(`-1.23e+45`), + []byte(`{"key": "value", "num": 42, "bool": true, "null": null}`), + []byte(`[1, 2, 3, "hello", null, true, false, 1.5e10]`), + []byte(`"hello\nworld\t"`), + []byte(`{"escaped":"quote\"inside"}`), + []byte(`1e308`), + []byte(`-1e308`), + []byte(`5e-324`), +} + +// FuzzSerializer builds a random value DOM from fuzz bytes, serializes it with +// ShapeSerializer, and verifies the output is valid JSON that round-trips to +// the same value through encoding/json. +func FuzzSerializer(f *testing.F) { + f.Add([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}) + f.Add([]byte{4, 5, 0xff, 0xfe, 0x00, 0x01, 0x1f, 0x7f, 0xc0, 0x80}) + f.Add([]byte{6, 3, 4, 2, 'h', 'i', 0, 1, 3, 0x80, 0xff, 0x22, 0x5c}) + + f.Fuzz(func(t *testing.T, data []byte) { + c := &fuzzConsumer{data: data} + dom := buildFuzzValue(c, 0) + + s := NewShapeSerializer() + writeFuzzValue(s, dom) + out := s.Bytes() + s.Close() + + var parsed any + if err := json.Unmarshal(out, &parsed); err != nil { + t.Fatalf("invalid JSON output: %v\nraw: %s", err, out) + } + + if expected, ok := normalizeForJSON(dom); ok { + if !reflect.DeepEqual(normalize(parsed), normalize(expected)) { + t.Errorf("round-trip mismatch\ndom: %v\nexpected: %v\nparsed: %v\nraw: %s", + dom, expected, parsed, out) + } + } + }) +} + +type fuzzConsumer struct { + data []byte + pos int +} + +func (c *fuzzConsumer) readByte() byte { + if c.pos >= len(c.data) { + return 0 + } + b := c.data[c.pos] + c.pos++ + return b +} + +func (c *fuzzConsumer) readBytes(n int) []byte { + if c.pos+n > len(c.data) { + n = len(c.data) - c.pos + } + b := c.data[c.pos : c.pos+n] + c.pos += n + return b +} + +func buildFuzzValue(c *fuzzConsumer, depth int) any { + if depth > 4 { + return nil + } + + switch c.readByte() % 7 { + case 0: + return nil + case 1: + return c.readByte()%2 == 0 + case 2: + return int64(int8(c.readByte())) + case 3: + b := c.readBytes(8) + if len(b) < 8 { + b = append(b, make([]byte, 8-len(b))...) + } + f := math.Float64frombits(binary.LittleEndian.Uint64(b)) + // exclude NaN/Inf — they serialize as strings, not numbers + if math.IsNaN(f) || math.IsInf(f, 0) { + return 0.0 + } + return f + case 4: + n := int(c.readByte() % 32) + return string(c.readBytes(n)) + case 5: + n := int(c.readByte() % 4) + list := make([]any, n) + for i := range list { + list[i] = buildFuzzValue(c, depth+1) + } + return list + case 6: + n := int(c.readByte() % 4) + m := make(map[string]any, n) + for range n { + k := string(c.readBytes(int(c.readByte() % 8))) + m[k] = buildFuzzValue(c, depth+1) + } + return m + } + return nil +} + +func writeFuzzValue(s *ShapeSerializer, v any) { + switch vv := v.(type) { + case nil: + s.WriteNil(nil) + case bool: + s.WriteBool(nil, vv) + case int64: + s.WriteInt64(nil, vv) + case float64: + s.WriteFloat64(nil, vv) + case string: + s.WriteString(nil, vv) + case []any: + s.WriteList(nil) + for _, item := range vv { + writeFuzzValue(s, item) + } + s.CloseList() + case map[string]any: + s.WriteMap(nil) + for k, item := range vv { + s.WriteKey(nil, k) + writeFuzzValue(s, item) + } + s.CloseMap() + } +} + +// normalizeForJSON converts our DOM types to what encoding/json would produce +// after unmarshal (all numbers become float64, nil in lists stays nil). +// Strings with invalid UTF-8 are normalized the same way the serializer +// handles them (replace invalid bytes with U+FFFD). +// Returns false if the value contains map key collisions after normalization, +// meaning a clean round-trip is impossible. +func normalizeForJSON(v any) (any, bool) { + switch vv := v.(type) { + case int64: + return float64(vv), true + case string: + return normalizeString(vv), true + case []any: + out := make([]any, len(vv)) + for i, item := range vv { + val, ok := normalizeForJSON(item) + if !ok { + return nil, false + } + out[i] = val + } + return out, true + case map[string]any: + out := make(map[string]any, len(vv)) + for k, item := range vv { + nk := normalizeString(k) + if _, exists := out[nk]; exists { + return nil, false + } + val, ok := normalizeForJSON(item) + if !ok { + return nil, false + } + out[nk] = val + } + return out, true + default: + return v, true + } +} + +func normalizeString(s string) string { + if utf8.ValidString(s) { + return s + } + + var b []byte + for i := 0; i < len(s); { + r, size := utf8.DecodeRuneInString(s[i:]) + if r == utf8.RuneError && size == 1 { + b = utf8.AppendRune(b, utf8.RuneError) + } else { + b = append(b, s[i:i+size]...) + } + i += size + } + return string(b) +} diff --git a/transport/http/protocol/internal/json/internal/stdlib/LICENSE b/transport/http/protocol/internal/json/internal/stdlib/LICENSE new file mode 100644 index 000000000..6a66aea5e --- /dev/null +++ b/transport/http/protocol/internal/json/internal/stdlib/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/transport/http/protocol/internal/json/internal/stdlib/stdlib.go b/transport/http/protocol/internal/json/internal/stdlib/stdlib.go new file mode 100644 index 000000000..fa84c43d8 --- /dev/null +++ b/transport/http/protocol/internal/json/internal/stdlib/stdlib.go @@ -0,0 +1,145 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package stdlib contains code copied from the Go standard library's +// encoding/json package (Go 1.24). +// +// We cannot use strconv.Unquote because JSON string rules differ from Go, +// JSON allows \/ and uses UTF-16 surrogate pairs. +// +// Source: https://cs.opensource.google/go/go/+/refs/tags/go1.24.0:src/encoding/json/decode.go +package stdlib + +import ( + "unicode" + "unicode/utf16" + "unicode/utf8" +) + +// Getu4 is copied from Go stdlib. +func Getu4(s []byte) rune { + if len(s) < 6 || s[0] != '\\' || s[1] != 'u' { + return -1 + } + var r rune + for _, c := range s[2:6] { + switch { + case '0' <= c && c <= '9': + c = c - '0' + case 'a' <= c && c <= 'f': + c = c - 'a' + 10 + case 'A' <= c && c <= 'F': + c = c - 'A' + 10 + default: + return -1 + } + r = r*16 + rune(c) + } + return r +} + + +// UnquoteBytes is copied from Go stdlib. +func UnquoteBytes(s []byte) (t []byte, ok bool) { + if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { + return + } + s = s[1 : len(s)-1] + + r := 0 + for r < len(s) { + c := s[r] + if c == '\\' || c == '"' || c < ' ' { + break + } + if c < utf8.RuneSelf { + r++ + continue + } + rr, size := utf8.DecodeRune(s[r:]) + if rr == utf8.RuneError && size == 1 { + break + } + r += size + } + if r == len(s) { + return s, true + } + + b := make([]byte, len(s)+2*utf8.UTFMax) + w := copy(b, s[0:r]) + for r < len(s) { + if w >= len(b)-2*utf8.UTFMax { + nb := make([]byte, (len(b)+utf8.UTFMax)*2) + copy(nb, b[0:w]) + b = nb + } + switch c := s[r]; { + case c == '\\': + r++ + if r >= len(s) { + return + } + switch s[r] { + default: + return + case '"', '\\', '/', '\'': + b[w] = s[r] + r++ + w++ + case 'b': + b[w] = '\b' + r++ + w++ + case 'f': + b[w] = '\f' + r++ + w++ + case 'n': + b[w] = '\n' + r++ + w++ + case 'r': + b[w] = '\r' + r++ + w++ + case 't': + b[w] = '\t' + r++ + w++ + case 'u': + r-- + rr := Getu4(s[r:]) + if rr < 0 { + return + } + r += 6 + if utf16.IsSurrogate(rr) { + rr1 := Getu4(s[r:]) + if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar { + r += 6 + w += utf8.EncodeRune(b[w:], dec) + break + } + rr = unicode.ReplacementChar + } + w += utf8.EncodeRune(b[w:], rr) + } + + case c == '"', c < ' ': + return + + case c < utf8.RuneSelf: + b[w] = c + r++ + w++ + + default: + rr, size := utf8.DecodeRune(s[r:]) + r += size + w += utf8.EncodeRune(b[w:], rr) + } + } + return b[0:w], true +} diff --git a/transport/http/protocol/internal/json/jsontestsuite_test.go b/transport/http/protocol/internal/json/jsontestsuite_test.go new file mode 100644 index 000000000..059d0df52 --- /dev/null +++ b/transport/http/protocol/internal/json/jsontestsuite_test.go @@ -0,0 +1,90 @@ +package json + +import ( + "bytes" + "encoding/json" + "os" + "path/filepath" + "strings" + "testing" +) + +var skipTests = map[string]bool{ + // Trailing content after a valid top-level value, like stdlib, we parse + // one complete value and stop. + "n_array_comma_after_close.json": true, + "n_array_extra_close.json": true, + "n_multidigit_number_then_00.json": true, + "n_object_trailing_comment.json": true, + "n_object_trailing_comment_open.json": true, + "n_object_trailing_comment_slash_open.json": true, + "n_object_trailing_comment_slash_open_incomplete.json": true, + "n_object_with_trailing_garbage.json": true, + "n_string_with_trailing_garbage.json": true, + "n_structure_array_trailing_garbage.json": true, + "n_structure_array_with_extra_array_close.json": true, + "n_structure_close_unopened_array.json": true, + "n_structure_double_array.json": true, + "n_structure_number_with_trailing_garbage.json": true, + "n_structure_object_followed_by_closing_object.json": true, + "n_structure_object_with_trailing_garbage.json": true, + "n_structure_trailing_#.json": true, + + // These numbers have valid JSON grammar but overflow float64, stdlib + // rejects them at tokenize, we reject in the ShapeDeserializer. + "i_number_huge_exp.json": true, + "i_number_neg_int_huge_exp.json": true, + "i_number_pos_double_huge_exp.json": true, + "i_number_real_neg_overflow.json": true, + "i_number_real_pos_overflow.json": true, +} + +// Run test cases from https://github.com/nst/JSONTestSuite. +func TestJSONTestSuite(t *testing.T) { + entries, err := os.ReadDir("testdata/test_parsing") + if err != nil { + t.Fatal(err) + } + + for _, ent := range entries { + if !strings.HasSuffix(ent.Name(), ".json") { + continue + } + + t.Run(ent.Name(), func(t *testing.T) { + if skipTests[ent.Name()] { + t.Skip() + } + + data, err := os.ReadFile(filepath.Join("testdata/test_parsing", ent.Name())) + if err != nil { + t.Fatal(err) + } + + ours := testParse(data) + switch { + case strings.HasPrefix(ent.Name(), "y_"): // should accept + if ours != nil { + t.Errorf("must accept, got error: %v", ours) + } + case strings.HasPrefix(ent.Name(), "n_"): // should reject + if ours == nil { + t.Error("must reject") + } + case strings.HasPrefix(ent.Name(), "i_"): // implementation-specific, ensure we match stdlib + var v any + theirs := json.NewDecoder(bytes.NewReader(data)).Decode(&v) + if (ours == nil) != (theirs == nil) { + t.Errorf("stdlib mismatch: us=%v stdlib=%v", errstr(ours), errstr(theirs)) + } + } + }) + } +} + +func errstr(err error) string { + if err == nil { + return "accept" + } + return "reject: " + err.Error() +} diff --git a/transport/http/protocol/internal/json/parser.go b/transport/http/protocol/internal/json/parser.go new file mode 100644 index 000000000..2a4e9d173 --- /dev/null +++ b/transport/http/protocol/internal/json/parser.go @@ -0,0 +1,466 @@ +package json + +import ( + "errors" + "fmt" + "io" + "strconv" + "unsafe" + + "github.com/aws/smithy-go/internal/serde" +) + +// matches stdlib +const maxDepth = 10_000 + +const ( + inObject int8 = iota + 1 + inArray +) + +type parseState uint8 + +const ( + stValue parseState = iota + stObjectKey + stObjectColon + stObjectComma + stObjectKeyAfterComma + stArrayValue + stArrayComma +) + +func errUnexpectedToken(c byte) error { + return fmt.Errorf("unexpected token '%c'", c) +} + +// parser is a combined scanner+validator that pulls tokens from a JSON body. +// It integrates tokenization and grammar validation into a single loop to +// minimize function call overhead on the hot path. +type parser struct { + p []byte + i int + stack serde.Stack[int8] + done bool + state parseState + + // set by scanString: true if the last string token contained escapes + escaped bool +} + +func (p *parser) Next() ([]byte, error) { + if p.done { + return nil, io.EOF + } + + for { + // inline whitespace scanning + token identification + i := p.i + buf := p.p + for ; i < len(buf); i++ { + if charClass[buf[i]] != ccWhite { + break + } + } + if i >= len(buf) { + p.i = i + return nil, fmt.Errorf("unexpected end of JSON input") + } + + c := buf[i] + p.i = i + + // get the token + var next []byte + var err error + if charClass[c] == ccDelim { + p.i = i + 1 + next = buf[i : i+1] + } else { + switch c { + case '"': + next, err = p.scanString() + case 't': + next, err = p.scanLiteral("true") + case 'f': + next, err = p.scanLiteral("false") + case 'n': + next, err = p.scanLiteral("null") + default: + next, err = p.scanNumber() + } + if err != nil { + return nil, err + } + } + + // validate grammar + switch p.state { + case stValue: + switch next[0] { + case '{': + p.state = stObjectKey + p.stack.Push(inObject) + if p.stack.Len() > maxDepth { + return nil, errors.New("exceeded max nesting depth") + } + return next, nil + case '[': + p.state = stArrayValue + p.stack.Push(inArray) + if p.stack.Len() > maxDepth { + return nil, errors.New("exceeded max nesting depth") + } + return next, nil + case ',', ':', '}', ']': + return nil, errUnexpectedToken(next[0]) + } + p.afterValue() + return next, nil + + case stObjectKey: + switch next[0] { + case '"': + p.state = stObjectColon + return next, nil + case '}': + p.close() + return next, nil + default: + return nil, errUnexpectedToken(next[0]) + } + + case stObjectColon: + if next[0] != ':' { + return nil, errUnexpectedToken(next[0]) + } + p.state = stValue + continue + + case stObjectComma: + switch next[0] { + case ',': + p.state = stObjectKeyAfterComma + continue + case '}': + p.close() + return next, nil + default: + return nil, errUnexpectedToken(next[0]) + } + + case stObjectKeyAfterComma: + if next[0] != '"' { + return nil, errUnexpectedToken(next[0]) + } + p.state = stObjectColon + return next, nil + + case stArrayValue: + if next[0] == ']' { + p.close() + return next, nil + } + // handle as value + switch next[0] { + case '{': + p.state = stObjectKey + p.stack.Push(inObject) + if p.stack.Len() > maxDepth { + return nil, errors.New("exceeded max nesting depth") + } + return next, nil + case '[': + p.state = stArrayValue + p.stack.Push(inArray) + if p.stack.Len() > maxDepth { + return nil, errors.New("exceeded max nesting depth") + } + return next, nil + case ',', ':', '}': + return nil, errUnexpectedToken(next[0]) + } + p.afterValue() + return next, nil + + case stArrayComma: + switch next[0] { + case ',': + p.state = stValue + continue + case ']': + p.close() + return next, nil + default: + return nil, errUnexpectedToken(next[0]) + } + } + } +} + +func (p *parser) Skip() error { + var depth int + for { + tok, err := p.Next() + if err != nil { + return err + } + switch tok[0] { + case '{', '[': + depth++ + case '}', ']': + depth-- + } + if depth == 0 { + return nil + } + } +} + +func (p *parser) Value() (any, error) { + tok, err := p.Next() + if err != nil { + return nil, err + } + return p.value(tok) +} + +func (p *parser) value(tok []byte) (any, error) { + switch tok[0] { + case 'n': + return nil, nil + case 't': + return true, nil + case 'f': + return false, nil + case '"': + return unquote(tok) + case '{': + m := map[string]any{} + for { + ktok, err := p.Next() + if err != nil { + return nil, err + } + if ktok[0] == '}' { + return m, nil + } + key, err := unquote(ktok) + if err != nil { + return nil, err + } + val, err := p.Value() + if err != nil { + return nil, err + } + m[key] = val + } + case '[': + var list []any + for { + tok, err := p.Next() + if err != nil { + return nil, err + } + if tok[0] == ']' { + if list == nil { + list = []any{} + } + return list, nil + } + val, err := p.value(tok) + if err != nil { + return nil, err + } + list = append(list, val) + } + default: + return strconv.ParseFloat(string(tok), 64) + } +} + +func (p *parser) close() { + p.stack.Pop() + p.afterValue() +} + +func (p *parser) afterValue() { + top := p.stack.Top() + if top == nil { + p.done = true + return + } + + switch *top { + case inObject: + p.state = stObjectComma + case inArray: + p.state = stArrayComma + default: + p.done = true + } +} + +// --- Integrated scanner methods --- + +const ( + lo = uint64(0x0101010101010101) + hi = uint64(0x8080808080808080) +) + +func hasValue(v uint64, c byte) uint64 { + x := v ^ (lo * uint64(c)) + return (x - lo) & ^x & hi +} + +func hasLess(v uint64, n byte) uint64 { + return (v - lo*uint64(n)) & ^v & hi +} + +func (p *parser) scanString() ([]byte, error) { + start := p.i + i := p.i + 1 // skip opening " + p.escaped = false + + // SWAR: scan 8 bytes at a time for '"', '\', or control chars (< 0x20) + for i+8 <= len(p.p) { + v := *(*uint64)(unsafe.Pointer(&p.p[i])) + mask := hasLess(v, 0x20) | hasValue(v, '"') | hasValue(v, '\\') + if mask != 0 { + break + } + i += 8 + } + + for i < len(p.p) { + c := p.p[i] + if c < 0x20 { + p.i = i + return nil, fmt.Errorf("invalid control character at offset %d", i) + } + + if c == '\\' { + p.escaped = true + p.i = i + 1 + if err := p.scanEscape(); err != nil { + return nil, err + } + i = p.i + continue + } + + if c == '"' { + i++ + p.i = i + return p.p[start:i], nil + } + + i++ + } + p.i = i + return nil, fmt.Errorf("unterminated string at offset %d", start) +} + +func (p *parser) scanEscape() error { + if p.i >= len(p.p) { + return fmt.Errorf("unterminated escape at offset %d", p.i-1) + } + c := p.p[p.i] + p.i++ + switch c { + case '"', '\\', '/', 'b', 'f', 'n', 'r', 't': + return nil + case 'u': + return p.scanUnicodeEscape() + default: + return fmt.Errorf("invalid escape character '%c' at offset %d", c, p.i-1) + } +} + +func (p *parser) scanUnicodeEscape() error { + if p.i+4 > len(p.p) { + return fmt.Errorf("incomplete unicode escape at offset %d", p.i-2) + } + for _, c := range p.p[p.i : p.i+4] { + if !('0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F') { + return fmt.Errorf("invalid character '%c' in unicode escape at offset %d", c, p.i-2) + } + } + p.i += 4 + return nil +} + +func (p *parser) scanNumber() ([]byte, error) { + i := p.i + if i < len(p.p) && p.p[i] == '-' { + i++ + } + digitStart := i + for i < len(p.p) && p.p[i] >= '0' && p.p[i] <= '9' { + i++ + } + if i == digitStart { + return nil, fmt.Errorf("unexpected token at offset %d", p.i) + } + if p.p[digitStart] == '0' && i-digitStart > 1 { + return nil, fmt.Errorf("leading zeros not allowed at offset %d", digitStart) + } + + if i < len(p.p) && p.p[i] == '.' { + i++ + digitStart = i + for i < len(p.p) && p.p[i] >= '0' && p.p[i] <= '9' { + i++ + } + if i == digitStart { + return nil, fmt.Errorf("no digits after decimal point at offset %d", i) + } + } + + if i < len(p.p) && (p.p[i] == 'e' || p.p[i] == 'E') { + i++ + if i < len(p.p) && (p.p[i] == '+' || p.p[i] == '-') { + i++ + } + digitStart = i + for i < len(p.p) && p.p[i] >= '0' && p.p[i] <= '9' { + i++ + } + if i == digitStart { + return nil, fmt.Errorf("no digits after exponent at offset %d", i) + } + } + + start := p.i + p.i = i + return p.p[start:i], nil +} + +func (p *parser) scanLiteral(lit string) ([]byte, error) { + end := p.i + len(lit) + if end > len(p.p) || string(p.p[p.i:end]) != lit { + return nil, fmt.Errorf("invalid literal at offset %d", p.i) + } + start := p.i + p.i = end + return p.p[start:end], nil +} + +const ( + ccNone byte = 0 + ccWhite byte = 1 + ccDelim byte = 2 +) + +var charClass = [256]byte{ + ' ': ccWhite, + '\t': ccWhite, + '\n': ccWhite, + '\r': ccWhite, + '{': ccDelim, + '}': ccDelim, + '[': ccDelim, + ']': ccDelim, + ':': ccDelim, + ',': ccDelim, +} diff --git a/transport/http/protocol/internal/json/scanner.go b/transport/http/protocol/internal/json/scanner.go new file mode 100644 index 000000000..a5b981cc6 --- /dev/null +++ b/transport/http/protocol/internal/json/scanner.go @@ -0,0 +1 @@ +package json diff --git a/transport/http/protocol/internal/json/scanner_test.go b/transport/http/protocol/internal/json/scanner_test.go new file mode 100644 index 000000000..57eadedd8 --- /dev/null +++ b/transport/http/protocol/internal/json/scanner_test.go @@ -0,0 +1,557 @@ +// Tests in this file are derived from the Go standard library's encoding/json +// to validate matching behavior. +// +// see https://cs.opensource.google/go/go/+/refs/tags/go1.24.0:src/encoding/json/ + +package json + +import ( + "io" + "math" + "reflect" + "strings" + "testing" +) + +func testParse(input []byte) error { + pr := parser{ + p: input, + state: stValue, + } + for { + _, err := pr.Next() + if err == io.EOF { + return nil + } + if err != nil { + return err + } + } +} + +// --------------------------------------------------------------------------- +// From: src/encoding/json/scanner_test.go — TestValid +// +// Tests basic JSON validity detection. +// --------------------------------------------------------------------------- + +func TestValid(t *testing.T) { + // Cases from the stdlib TestValid table. + tests := []struct { + data string + ok bool + }{ + {`foo`, false}, + {`}{`, false}, + {`{]`, false}, + {`{}`, true}, + {`{"foo":"bar"}`, true}, + {`{"foo":"bar","bar":{"baz":["qux"]}}`, true}, + } + + // Additional cases exercising our scanner. + additional := []struct { + data string + ok bool + }{ + {`[]`, true}, + {`[1,2,3]`, true}, + {`null`, true}, + {`true`, true}, + {`false`, true}, + {`"hello"`, true}, + {`42`, true}, + {`-1.5e10`, true}, + } + tests = append(tests, additional...) + + for _, tt := range tests { + err := testParse([]byte(tt.data)) + got := err == nil + if got != tt.ok { + t.Errorf("drain(%q) valid=%v, want %v (err=%v)", tt.data, got, tt.ok, err) + } + } +} + +// --------------------------------------------------------------------------- +// From: src/encoding/json/decode_test.go — TestUnmarshalSyntax +// +// Tests that syntactically invalid JSON produces errors. +// --------------------------------------------------------------------------- + +func TestSyntaxErrors(t *testing.T) { + // Cases from the stdlib TestUnmarshalSyntax table. + stdlibCases := []string{ + "tru", + "fals", + "nul", + `"hello`, + `[1,2,3`, + `{"key":1`, + `{"key":1,`, + } + + // Additional cases from stdlib unmarshalTests (syntax error entries). + fromUnmarshalTests := []string{ + `{"X": "foo", "Y"}`, // missing colon + `[1, 2, 3+]`, // invalid char after element + `[2, 3`, // unexpected EOF + } + + // Additional cases exercising our parser's state machine. + additional := []string{ + `{]`, + `[}`, + `{"a" "b"}`, // missing colon + `[1 2]`, // missing comma + `{"a":}`, // missing value + `{:1}`, // missing key + `[,]`, // leading comma + `{,}`, // leading comma + `[1,,2]`, // double comma + } + + all := append(stdlibCases, fromUnmarshalTests...) + all = append(all, additional...) + + for _, tt := range all { + if err := testParse([]byte(tt)); err == nil { + t.Errorf("drain(%q) = nil, want error", tt) + } + } +} + +// --------------------------------------------------------------------------- +// From: src/encoding/json/stream_test.go — TestDecodeInStream +// +// Tests that the parser produces the correct token sequence. The stdlib test +// uses json.Token (interface values); we compare raw byte tokens instead since +// our parser returns []byte slices. +// --------------------------------------------------------------------------- + +func TestTokenStream(t *testing.T) { + // Cases from the stdlib TestDecodeInStream table (token-only entries, + // excluding decodeThis cases which test Decode-into-value). + tests := []struct { + json string + tokens []string + }{ + {`10`, []string{`10`}}, + {` [10] `, []string{`[`, `10`, `]`}}, + {` [false,10,"b"] `, []string{`[`, `false`, `10`, `"b"`, `]`}}, + {`{ "a": 1 }`, []string{`{`, `"a"`, `1`, `}`}}, + {`{"a": 1, "b":"3"}`, []string{`{`, `"a"`, `1`, `"b"`, `"3"`, `}`}}, + {` [{"a": 1},{"a": 2}] `, []string{ + `[`, `{`, `"a"`, `1`, `}`, `{`, `"a"`, `2`, `}`, `]`, + }}, + {`{"obj": {"a": 1}}`, []string{ + `{`, `"obj"`, `{`, `"a"`, `1`, `}`, `}`, + }}, + {`{"obj": [{"a": 1}]}`, []string{ + `{`, `"obj"`, `[`, `{`, `"a"`, `1`, `}`, `]`, `}`, + }}, + } + + // Additional cases. + additional := []struct { + json string + tokens []string + }{ + {`null`, []string{`null`}}, + {`true`, []string{`true`}}, + {`false`, []string{`false`}}, + {`"hello"`, []string{`"hello"`}}, + {`""`, []string{`""`}}, + {`[null, true, false]`, []string{`[`, `null`, `true`, `false`, `]`}}, + } + tests = append(tests, additional...) + + for _, tt := range tests { + t.Run(tt.json, func(t *testing.T) { + p := parser{ + p: []byte(tt.json), + state: stValue, + } + var got []string + for { + tok, err := p.Next() + if err == io.EOF { + break + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + got = append(got, string(tok)) + } + if !reflect.DeepEqual(got, tt.tokens) { + t.Errorf("tokens mismatch:\n got: %v\n want: %v", got, tt.tokens) + } + }) + } +} + +// --------------------------------------------------------------------------- +// From: src/encoding/json/decode_test.go — unmarshalTests +// +// Tests that parser.Value() decodes JSON into Go values matching the behavior +// of encoding/json.Decoder.Decode into any (numbers become float64, objects +// become map[string]any, arrays become []any). +// --------------------------------------------------------------------------- + +func TestValue(t *testing.T) { + // Cases drawn from the stdlib unmarshalTests table (adapted for untyped + // decoding into any). + tests := []struct { + in string + out any + }{ + // Basic types — from unmarshalTests lines for bool, int, float, string. + {`true`, true}, + {`false`, false}, + {`null`, nil}, + {`1`, float64(1)}, + {`1.2`, float64(1.2)}, + {`-5`, float64(-5)}, + {`2`, float64(2)}, + {`0`, float64(0)}, + {`-0`, float64(0)}, + {`1e2`, float64(100)}, + {`1.5e1`, float64(15)}, + {`-5e+2`, float64(-500)}, + {`3e-3`, float64(0.003)}, + + // String escapes — from unmarshalTests. + {`"a\u1234"`, "a\u1234"}, + {`"http:\/\/"`, "http://"}, + {`"g-clef: \uD834\uDD1E"`, "g-clef: \U0001D11E"}, + {`"invalid: \uD834x\uDD1E"`, "invalid: \uFFFDx\uFFFD"}, + + // Whitespace — from unmarshalTests "raw values with whitespace" section. + {"\n true ", true}, + {"\t 1 ", float64(1)}, + {"\r 1.2 ", float64(1.2)}, + {"\t -5 \n", float64(-5)}, + {"\t \"a\\u1234\" \n", "a\u1234"}, + + // Complex nested — from unmarshalTests ifaceNumAsFloat64 case. + {`{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, + map[string]any{ + "k1": float64(1), + "k2": "s", + "k3": []any{float64(1), float64(2.0), float64(0.003)}, + "k4": map[string]any{"kk1": "s", "kk2": float64(2)}, + }}, + } + + // Additional cases. + additional := []struct { + in string + out any + }{ + {`"hello"`, "hello"}, + {`""`, ""}, + {`"tab:\t"`, "tab:\t"}, + {`"newline:\n"`, "newline:\n"}, + {`"quote:\""`, "quote:\""}, + {`"backslash:\\"`, "backslash:\\"}, + {`"slash:\/"`, "slash:/"}, + {`"cr:\r"`, "cr:\r"}, + {`"formfeed:\f"`, "formfeed:\f"}, + {`"backspace:\b"`, "backspace:\b"}, + {`[]`, []any{}}, + {`[1,2,3]`, []any{float64(1), float64(2), float64(3)}}, + {`[true, false, null]`, []any{true, false, nil}}, + {`["a", "b"]`, []any{"a", "b"}}, + {`[[1],[2]]`, []any{[]any{float64(1)}, []any{float64(2)}}}, + {`{}`, map[string]any{}}, + {`{"a":1}`, map[string]any{"a": float64(1)}}, + {`{"a":1,"b":"two"}`, map[string]any{"a": float64(1), "b": "two"}}, + {`{"nested":{"x":true}}`, map[string]any{"nested": map[string]any{"x": true}}}, + } + tests = append(tests, additional...) + + for _, tt := range tests { + t.Run(tt.in, func(t *testing.T) { + p := parser{ + p: []byte(tt.in), + state: stValue, + } + got, err := p.Value() + if err != nil { + t.Fatalf("Value(%q) error: %v", tt.in, err) + } + if !reflect.DeepEqual(got, tt.out) { + t.Errorf("Value(%q):\n got: %#v\n want: %#v", tt.in, got, tt.out) + } + }) + } +} + +// --------------------------------------------------------------------------- +// From: src/encoding/json/number_test.go — TestNumberIsValid +// +// Tests valid and invalid JSON number formats. +// --------------------------------------------------------------------------- + +func TestNumberParsing(t *testing.T) { + // Valid numbers from the stdlib TestNumberIsValid table. + valid := []string{ + "0", + "-0", + "1", + "-1", + "0.1", + "-0.1", + "1234", + "-1234", + "12.34", + "-12.34", + "12E0", + "12E1", + "12e34", + "12E-0", + "12e+1", + "12e-34", + "-12E0", + "-12E1", + "-12e34", + "-12E-0", + "-12e+1", + "-12e-34", + "1.2E0", + "1.2E1", + "1.2e34", + "1.2E-0", + "1.2e+1", + "1.2e-34", + "-1.2E0", + "-1.2E1", + "-1.2e34", + "-1.2E-0", + "-1.2e+1", + "-1.2e-34", + "0E0", + "0E1", + "0e34", + "0E-0", + "0e+1", + "0e-34", + "-0E0", + "-0E1", + "-0e34", + "-0E-0", + "-0e+1", + "-0e-34", + } + for _, tt := range valid { + if err := testParse([]byte(tt)); err != nil { + t.Errorf("drain(%q) = %v, want nil", tt, err) + } + } + + // Invalid numbers from the stdlib TestNumberIsValid table. + // Note: some stdlib cases (like "123e", "1e", "1e+", "01", "012") test + // isValidNumber() which rejects them statically. Our scanner may accept + // the token but the parser's state machine or strconv.ParseFloat will + // reject them downstream. We only include cases our scanner itself + // should reject. + invalid := []string{ + "", + "invalid", + "1..1", + "1e+-2", + "1e--23", + "e1", + "1ea", + "1.a", + } + for _, tt := range invalid { + if err := testParse([]byte(tt)); err == nil { + t.Errorf("drain(%q) = nil, want error", tt) + } + } +} + +// --------------------------------------------------------------------------- +// From: src/encoding/json/decode_test.go — unmarshalTests (string entries) +// +// Tests string escape sequences including JSON-specific escapes (\/) and +// UTF-16 surrogate pairs (\uD800\uDC00) that differ from Go string literals. +// --------------------------------------------------------------------------- + +func TestStringEscapes(t *testing.T) { + // Cases from stdlib unmarshalTests and the unquoteBytes implementation. + tests := []struct { + in string + want string + }{ + {`"\""`, `"`}, + {`"\\"`, `\`}, + {`"\/"`, `/`}, // JSON-specific: \/ is valid + {`"\b"`, "\b"}, + {`"\f"`, "\f"}, + {`"\n"`, "\n"}, + {`"\r"`, "\r"}, + {`"\t"`, "\t"}, + {`"\u0041"`, "A"}, + {`"\u00e9"`, "é"}, + {`"\u0000"`, "\x00"}, + {`"\uD800\uDC00"`, "\U00010000"}, // surrogate pair: U+10000 + {`"\uD834\uDD1E"`, "\U0001D11E"}, // surrogate pair: G clef + {`"no escape"`, "no escape"}, + {`""`, ""}, + } + for _, tt := range tests { + t.Run(tt.in, func(t *testing.T) { + tok := parser{p: []byte(tt.in), state: stValue} + raw, err := tok.Next() + if err != nil { + t.Fatalf("tokenize error: %v", err) + } + got, err := unquote(raw) + if err != nil { + t.Fatalf("str error: %v", err) + } + if got != tt.want { + t.Errorf("unquote(%s) = %q, want %q", tt.in, got, tt.want) + } + }) + } +} + +// --------------------------------------------------------------------------- +// Additional tests not directly from stdlib but validating equivalent behavior. +// --------------------------------------------------------------------------- + +func TestInvalidStrings(t *testing.T) { + tests := []string{ + `"`, // unterminated + `"hello`, // unterminated + "\"hello\n\"", // unescaped newline (invalid per RFC 8259 §7) + "\"hello\r\"", // unescaped carriage return + } + for _, tt := range tests { + tok := parser{p: []byte(tt), state: stValue} + _, err := tok.Next() + if err == nil { + t.Errorf("tokenize(%q) = nil, want error", tt) + } + } +} + +func TestFloatSpecialValues(t *testing.T) { + tests := []struct { + in string + out float64 + }{ + {"0", 0}, + {"-0", math.Copysign(0, -1)}, + {"1e308", 1e308}, + {"-1e308", -1e308}, + {"5e-324", 5e-324}, + {"1.7976931348623157e308", math.MaxFloat64}, + } + for _, tt := range tests { + t.Run(tt.in, func(t *testing.T) { + p := parser{ + p: []byte(tt.in), + state: stValue, + } + v, err := p.Value() + if err != nil { + t.Fatalf("Value(%q) error: %v", tt.in, err) + } + got, ok := v.(float64) + if !ok { + t.Fatalf("Value(%q) = %T, want float64", tt.in, v) + } + if got != tt.out { + t.Errorf("Value(%q) = %v, want %v", tt.in, got, tt.out) + } + }) + } +} + +func TestSkip(t *testing.T) { + tests := []struct { + json string + after string + }{ + {`[1,2,3], "after"`, "after"}, + {`{"a":1}, "after"`, "after"}, + {`"str", "after"`, "after"}, + {`123, "after"`, "after"}, + {`true, "after"`, "after"}, + {`null, "after"`, "after"}, + {`{"a":{"b":[1,2,{"c":3}]}}, "after"`, "after"}, + } + for _, tt := range tests { + t.Run(tt.json, func(t *testing.T) { + wrapped := `[` + tt.json + `]` + p := parser{ + p: []byte(wrapped), + state: stValue, + } + tok, err := p.Next() + if err != nil || string(tok) != "[" { + t.Fatalf("expected '[', got %q err=%v", tok, err) + } + if err := p.Skip(); err != nil { + t.Fatalf("Skip() error: %v", err) + } + tok, err = p.Next() + if err != nil { + t.Fatalf("Next() after Skip error: %v", err) + } + got, _ := unquote(tok) + if got != tt.after { + t.Errorf("after Skip: got %q, want %q", got, tt.after) + } + }) + } +} + +func TestLargeInput(t *testing.T) { + // Deeply nested object. + var b strings.Builder + depth := 100 + for i := 0; i < depth; i++ { + b.WriteString(`{"a":`) + } + b.WriteString(`1`) + for i := 0; i < depth; i++ { + b.WriteString(`}`) + } + if err := testParse([]byte(b.String())); err != nil { + t.Fatalf("drain(nested %d deep) error: %v", depth, err) + } + + // Large array. + b.Reset() + b.WriteString(`[`) + for i := 0; i < 10000; i++ { + if i > 0 { + b.WriteString(`,`) + } + b.WriteString(`{"key":"value","num":123}`) + } + b.WriteString(`]`) + if err := testParse([]byte(b.String())); err != nil { + t.Fatalf("drain(large array) error: %v", err) + } +} + +func TestWhitespace(t *testing.T) { + tests := []string{ + " \t\r\n{} ", + " [ 1 , 2 , 3 ] ", + "\n\n{\n\"a\"\n:\n1\n}\n", + "\t{\t\"key\"\t:\t\"value\"\t}\t", + } + for _, tt := range tests { + if err := testParse([]byte(tt)); err != nil { + t.Errorf("drain(%q) = %v, want nil", tt, err) + } + } +} diff --git a/transport/http/protocol/internal/json/shape_deserializer.go b/transport/http/protocol/internal/json/shape_deserializer.go new file mode 100644 index 000000000..fe6bba02d --- /dev/null +++ b/transport/http/protocol/internal/json/shape_deserializer.go @@ -0,0 +1,854 @@ +package json + +import ( + "encoding/base64" + "fmt" + "math" + "math/big" + "strconv" + "strings" + "sync" + "time" + "unsafe" + + "github.com/aws/smithy-go" + "github.com/aws/smithy-go/transport/http/protocol/internal/json/internal/stdlib" + "github.com/aws/smithy-go/document" + "github.com/aws/smithy-go/internal/serde" + smithytime "github.com/aws/smithy-go/time" + "github.com/aws/smithy-go/traits" +) + +type ctxKind int8 + +const ( + ctxList ctxKind = iota + 1 + ctxMap + ctxStruct + ctxUnion +) + +type deserCtx struct { + kind ctxKind + schema *smithy.Schema // for ctxStruct +} + +// ShapeDeserializer implements unmarshaling of JSON into Smithy shapes. +type ShapeDeserializer struct { + p parser + head serde.Stack[deserCtx] + opts Options + + peeked []byte + peekedEscaped bool +} + +var deserPool = sync.Pool{ + New: func() any { + return &ShapeDeserializer{ + p: parser{stack: serde.NewStack[int8]()}, + head: serde.NewStack[deserCtx](), + } + }, +} + +// NewShapeDeserializer creates a new ShapeDeserializer. +func NewShapeDeserializer(p []byte, opts ...func(*Options)) *ShapeDeserializer { + o := Options{} + for _, fn := range opts { + fn(&o) + } + d := deserPool.Get().(*ShapeDeserializer) + d.p.p = p + d.p.i = 0 + d.p.state = stValue + d.p.done = false + d.p.stack.Reset() + d.head.Reset() + d.peeked = nil + d.opts = o + return d +} + +// Close returns the deserializer to the pool for reuse. +func (d *ShapeDeserializer) Close() { + deserPool.Put(d) +} + +var _ smithy.ShapeDeserializer = (*ShapeDeserializer)(nil) + +func (d *ShapeDeserializer) next() ([]byte, error) { + if d.peeked != nil { + peeked := d.peeked + d.peeked = nil + d.p.escaped = d.peekedEscaped + return peeked, nil + } + return d.p.Next() +} + +func (d *ShapeDeserializer) peek() ([]byte, error) { + if d.peeked != nil { + return d.peeked, nil + } + tok, err := d.p.Next() + if err != nil { + return nil, err + } + d.peeked = tok + d.peekedEscaped = d.p.escaped + return tok, nil +} + +// ReadNil implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadNil(s *smithy.Schema) (bool, error) { + tok, err := d.peek() + if err != nil { + return false, err + } + if isN(tok) { + d.peeked = nil + return true, nil + } + return false, nil +} + +// ReadInt8 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadInt8(s *smithy.Schema, v *int8) error { + n, err := d.readInt(math.MinInt8, math.MaxInt8) + *v = int8(n) + return err +} + +// ReadInt16 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadInt16(s *smithy.Schema, v *int16) error { + n, err := d.readInt(math.MinInt16, math.MaxInt16) + *v = int16(n) + return err +} + +// ReadInt32 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadInt32(s *smithy.Schema, v *int32) error { + n, err := d.readInt(math.MinInt32, math.MaxInt32) + *v = int32(n) + return err +} + +// ReadInt64 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadInt64(s *smithy.Schema, v *int64) error { + n, err := d.readInt(math.MinInt64, math.MaxInt64) + *v = n + return err +} + +func (d *ShapeDeserializer) readInt(min, max int64) (int64, error) { + tok, err := d.next() + if err != nil { + return 0, err + } + + if isS(tok) || isLCB(tok) || isLSB(tok) { + return 0, fmt.Errorf("expected number, got %s", tok) + } + + n, ok := parseInt(tok) + if !ok { + return 0, fmt.Errorf("invalid int: %s", tok) + } + + if n < min || n > max { + return 0, fmt.Errorf("int %d exceeds range [%d, %d]", n, min, max) + } + + return n, nil +} + +// parseInt parses a decimal int directly from bytes, avoiding string alloc. +func parseInt(b []byte) (int64, bool) { + if len(b) == 0 { + return 0, false + } + neg := false + if b[0] == '-' { + neg = true + b = b[1:] + if len(b) == 0 { + return 0, false + } + } + + const cutoff = math.MaxUint64/10 + 1 + var n uint64 + for _, c := range b { + if c < '0' || c > '9' { + return 0, false + } + if n >= cutoff { + return 0, false + } + nn := n*10 + uint64(c-'0') + if nn < n { + return 0, false + } + n = nn + if n > uint64(math.MaxInt64) { + if neg && n == uint64(math.MaxInt64)+1 { + return math.MinInt64, true + } + return 0, false + } + } + + if neg { + return -int64(n), true + } + return int64(n), true +} + +// ReadFloat32 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadFloat32(s *smithy.Schema, v *float32) error { + n, err := d.readFloat() + *v = float32(n) + return err +} + +// ReadFloat64 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadFloat64(s *smithy.Schema, v *float64) error { + n, err := d.readFloat() + *v = n + return err +} + +func (d *ShapeDeserializer) readFloat() (float64, error) { + tok, err := d.next() + if err != nil { + return 0, err + } + + if isS(tok) { + s, err := unquote(tok) + if err != nil { + return 0, err + } + switch { + case strings.EqualFold(s, "NaN"): + return math.NaN(), nil + case strings.EqualFold(s, "Infinity"): + return math.Inf(1), nil + case strings.EqualFold(s, "-Infinity"): + return math.Inf(-1), nil + default: + return 0, fmt.Errorf("unexpected string value for float: %s", s) + } + } + + // fast path: if it's a plain integer, parse directly without alloc + if n, ok := parseInt(tok); ok { + return float64(n), nil + } + return strconv.ParseFloat(unsafeString(tok), 64) +} + +// ReadBool implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadBool(s *smithy.Schema, v *bool) error { + tok, err := d.next() + if err != nil { + return err + } + + switch { + case isT(tok): + *v = true + return nil + case isF(tok): + *v = false + return nil + default: + return fmt.Errorf("expected bool, got %s", tok) + } +} + +// ReadString implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadString(s *smithy.Schema, v *string) error { + tok, err := d.next() + if err != nil { + return err + } + if tok == nil { + return nil + } + + if !isS(tok) { + return fmt.Errorf("expected string, got %s", tok) + } + + if !d.p.escaped { + *v = unsafeString(tok[1 : len(tok)-1]) + return nil + } + + sv, err := unquote(tok) + if err != nil { + return err + } + + *v = sv + return nil +} + +// ReadTime implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadTime(schema *smithy.Schema, v *time.Time) error { + format := "epoch-seconds" + if t, ok := smithy.SchemaTrait[*traits.TimestampFormat](schema); ok { + format = t.Format + } + + switch format { + case "epoch-seconds": + n, err := d.readFloat() + if err != nil { + return err + } + *v = smithytime.ParseEpochSeconds(n) + return nil + case "date-time": + var s string + if err := d.ReadString(schema, &s); err != nil { + return err + } + t, err := smithytime.ParseDateTime(s) + if err != nil { + return err + } + *v = t + return nil + case "http-date": + var s string + if err := d.ReadString(schema, &s); err != nil { + return err + } + t, err := smithytime.ParseHTTPDate(s) + if err != nil { + return err + } + *v = t + return nil + default: + return fmt.Errorf("unknown timestamp format: %s", format) + } +} + +// ReadBlob implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadBlob(s *smithy.Schema, v *[]byte) error { + if isNil, err := d.ReadNil(s); isNil || err != nil { + return err + } + + tok, err := d.next() + if err != nil { + return err + } + + if !isS(tok) { + return fmt.Errorf("expected string, got %s", tok) + } + + sv, err := unquote(tok) + if err != nil { + return err + } + + b, err := base64.StdEncoding.DecodeString(sv) + if err != nil { + return fmt.Errorf("decode base64 blob: %w", err) + } + + *v = b + return nil +} + +// ReadList implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadList(s *smithy.Schema) error { + tok, err := d.next() + if err != nil { + return err + } + if !isLSB(tok) { + return fmt.Errorf("expected '[', got %s", tok) + } + d.head.Push(deserCtx{kind: ctxList}) + return nil +} + +// ReadListItem implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadListItem(s *smithy.Schema) (bool, error) { + tok, err := d.peek() + if err != nil { + return false, err + } + if isRSB(tok) { + d.peeked = nil + d.head.Pop() + return false, nil + } + return true, nil +} + +// ReadMap implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadMap(s *smithy.Schema) error { + tok, err := d.next() + if err != nil { + return err + } + if !isLCB(tok) { + return fmt.Errorf("expected '{', got %s", tok) + } + d.head.Push(deserCtx{kind: ctxMap}) + return nil +} + +// ReadMapKey implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadMapKey(s *smithy.Schema) (string, bool, error) { + tok, err := d.next() + if err != nil { + return "", false, err + } + if isRCB(tok) { + d.head.Pop() + return "", false, nil + } + + if !d.p.escaped { + return unsafeString(tok[1 : len(tok)-1]), true, nil + } + + key, err := unquote(tok) + if err != nil { + return "", false, err + } + return key, true, nil +} + +// ReadStruct implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadStruct(s *smithy.Schema) error { + if isNil, err := d.ReadNil(s); isNil || err != nil { + return err + } + + tok, err := d.next() + if err != nil { + return err + } + if !isLCB(tok) { + return fmt.Errorf("expected '{', got %s", tok) + } + d.head.Push(deserCtx{kind: ctxStruct, schema: s}) + return nil +} + +// ReadStructMember implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadStructMember() (*smithy.Schema, error) { + tok, err := d.next() + if err != nil { + return nil, err + } + if isRCB(tok) { + d.head.Pop() + return nil, nil + } + + top := d.head.Top() + if top == nil || top.kind != ctxStruct { + return nil, fmt.Errorf("ReadStructMember called without ReadStruct?") + } + + member, err := memberFromToken(top.schema, tok, d.p.escaped) + if err != nil { + return nil, err + } + + if member == nil && d.opts.UseJSONName { + key, err := unquote(tok) + if err != nil { + return nil, err + } + for _, m := range top.schema.Members() { + if jn, ok := smithy.SchemaTrait[*traits.JSONName](m); ok && jn.Name == key { + member = m + break + } + } + } + if member == nil { + if err := d.p.Skip(); err != nil { + return nil, err + } + return d.ReadStructMember() + } + + // inline null check to avoid function call overhead of ReadNil + ptok, err := d.peek() + if err != nil { + return nil, err + } + if isN(ptok) { + d.peeked = nil + return d.ReadStructMember() + } + + return member, nil +} + +// ReadUnion implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadUnion(s *smithy.Schema) (*smithy.Schema, error) { + if top := d.head.Top(); top == nil || top.kind != ctxUnion { + if isNil, err := d.ReadNil(s); isNil || err != nil { + return nil, err + } + + tok, err := d.next() + if err != nil { + return nil, err + } + if !isLCB(tok) { + return nil, fmt.Errorf("expected '{', got %s", tok) + } + d.head.Push(deserCtx{kind: ctxUnion}) + } + + for { + tok, err := d.next() + if err != nil { + return nil, err + } + if isRCB(tok) { + d.head.Pop() + return nil, nil + } + + // save escaped before peek overwrites it + keyEscaped := d.p.escaped + + // inline null check + ptok, err := d.peek() + if err != nil { + return nil, err + } + if isN(ptok) { + d.peeked = nil + continue + } + + member, err := memberFromToken(s, tok, keyEscaped) + if err != nil { + return nil, err + } + + if member == nil && d.opts.UseJSONName { + key, err := unquote(tok) + if err != nil { + return nil, err + } + for _, m := range s.Members() { + if jn, ok := smithy.SchemaTrait[*traits.JSONName](m); ok && jn.Name == key { + member = m + break + } + } + } + if member == nil { + if err := d.p.Skip(); err != nil { + return nil, err + } + continue + } + + return member, nil + } +} + +// ReadDocument reads a JSON value into a document Value. +func (d *ShapeDeserializer) ReadDocument(schema *smithy.Schema, v *document.Value) error { + tok, err := d.next() + if err != nil { + return err + } + vv, err := d.p.value(tok) + if err != nil { + return err + } + *v = document.Opaque{Value: vv} + return nil +} + +func unsafeString(b []byte) string { + return unsafe.String(unsafe.SliceData(b), len(b)) +} + +func unquote(tok []byte) (string, error) { + if s, ok := stdlib.UnquoteBytes(tok); ok { + return unsafeString(s), nil + } + return "", fmt.Errorf("cannot unquote %s", tok) +} + +func memberFromToken(s *smithy.Schema, tok []byte, escaped bool) (*smithy.Schema, error) { + inner := tok[1 : len(tok)-1] + if m := memberByBytes(s, inner); m != nil { + return m, nil + } + + // if the string had no escapes, the raw bytes ARE the unquoted form -- + // no point re-trying the lookup + if !escaped { + return nil, nil + } + + unq, err := unquote(tok) + if err != nil { + return nil, err + } + + return s.Member(unq), nil +} + +func isN(tok []byte) bool { return tok[0] == 'n' } +func isT(tok []byte) bool { return tok[0] == 't' } +func isF(tok []byte) bool { return tok[0] == 'f' } +func isS(tok []byte) bool { return tok[0] == '"' } +func isLCB(tok []byte) bool { return tok[0] == '{' } +func isRCB(tok []byte) bool { return tok[0] == '}' } +func isLSB(tok []byte) bool { return tok[0] == '[' } +func isRSB(tok []byte) bool { return tok[0] == ']' } + +// ReadBigInt is unimplemented and will return an error. +func (d *ShapeDeserializer) ReadBigInt(_ *smithy.Schema, _ *big.Int) error { + return fmt.Errorf("unimplemented") +} + +// ReadBigFloat is unimplemented and will return an error. +func (d *ShapeDeserializer) ReadBigFloat(_ *smithy.Schema, _ *big.Float) error { + return fmt.Errorf("unimplemented") +} + +// DirectReadStruct is a concrete-type fast path that avoids interface dispatch. +// It skips the head stack and reads struct members directly. +func (d *ShapeDeserializer) DirectReadStruct(schema *smithy.Schema, memberFn func(*smithy.Schema) error) error { + // null check + tok, err := d.peek() + if err != nil { + return err + } + if isN(tok) { + d.peeked = nil + return nil + } + + // consume '{' + tok, err = d.next() + if err != nil { + return err + } + if !isLCB(tok) { + return fmt.Errorf("expected '{', got %s", tok) + } + + for { + tok, err = d.next() + if err != nil { + return err + } + if isRCB(tok) { + return nil + } + + keyEscaped := d.p.escaped + + member, err := memberFromToken(schema, tok, keyEscaped) + if err != nil { + return err + } + + if member == nil && d.opts.UseJSONName { + key, qerr := unquote(tok) + if qerr != nil { + return qerr + } + for _, m := range schema.Members() { + if jn, ok := smithy.SchemaTrait[*traits.JSONName](m); ok && jn.Name == key { + member = m + break + } + } + } + if member == nil { + if err := d.p.Skip(); err != nil { + return err + } + continue + } + + // inline null check for the value + ptok, err := d.peek() + if err != nil { + return err + } + if isN(ptok) { + d.peeked = nil + continue + } + + if err := memberFn(member); err != nil { + return err + } + } +} + +// DirectReadUnion is a concrete-type fast path that avoids interface dispatch. +// It opens the union object, finds the single non-null member, calls memberFn, +// then drains to the closing brace. +func (d *ShapeDeserializer) DirectReadUnion(schema *smithy.Schema, memberFn func(*smithy.Schema) error) error { + // open phase: consume '{' (or 'null') + tok, err := d.next() + if err != nil { + return err + } + if isN(tok) { + return nil + } + if !isLCB(tok) { + return fmt.Errorf("expected '{', got %s", tok) + } + + // find the single non-null member + var member *smithy.Schema + for { + tok, err = d.next() + if err != nil { + return err + } + if isRCB(tok) { + return nil + } + + keyEscaped := d.p.escaped + + ptok, err := d.peek() + if err != nil { + return err + } + if isN(ptok) { + d.peeked = nil + continue + } + + member, err = memberFromToken(schema, tok, keyEscaped) + if err != nil { + return err + } + + if member == nil && d.opts.UseJSONName { + key, err := unquote(tok) + if err != nil { + return err + } + for _, m := range schema.Members() { + if jn, ok := smithy.SchemaTrait[*traits.JSONName](m); ok && jn.Name == key { + member = m + break + } + } + } + if member == nil { + if err := d.p.Skip(); err != nil { + return err + } + continue + } + + break + } + + // call the member function + if err := memberFn(member); err != nil { + return err + } + + // drain remaining members to closing '}' + for { + tok, err = d.next() + if err != nil { + return err + } + if isRCB(tok) { + return nil + } + + // skip any extra keys (lenient: tolerate services sending extra fields) + if err := d.p.Skip(); err != nil { + return err + } + } +} + +// DirectReadMap is a concrete-type fast path that avoids interface dispatch. +// It skips the head stack and reads map entries directly. +func (d *ShapeDeserializer) DirectReadMap(schema *smithy.Schema, memberFn func(string) error) error { + tok, err := d.next() + if err != nil { + return err + } + if !isLCB(tok) { + return fmt.Errorf("expected '{', got %s", tok) + } + + for { + tok, err = d.next() + if err != nil { + return err + } + if isRCB(tok) { + return nil + } + + var key string + if !d.p.escaped { + key = unsafeString(tok[1 : len(tok)-1]) + } else { + key, err = unquote(tok) + if err != nil { + return err + } + } + + if err := memberFn(key); err != nil { + return err + } + } +} + +// DirectReadList is a concrete-type fast path that avoids interface dispatch. +// It skips the head stack and reads list elements directly using peek. +func (d *ShapeDeserializer) DirectReadList(schema *smithy.Schema, memberFn func() error) error { + tok, err := d.next() + if err != nil { + return err + } + if !isLSB(tok) { + return fmt.Errorf("expected '[', got %s", tok) + } + + for { + tok, err = d.peek() + if err != nil { + return err + } + if isRSB(tok) { + d.peeked = nil + return nil + } + if err := memberFn(); err != nil { + return err + } + } +} diff --git a/transport/http/protocol/internal/json/shape_serializer.go b/transport/http/protocol/internal/json/shape_serializer.go new file mode 100644 index 000000000..3eba58160 --- /dev/null +++ b/transport/http/protocol/internal/json/shape_serializer.go @@ -0,0 +1,562 @@ +package json + +import ( + "encoding/base64" + "math" + "math/big" + "strconv" + "sync" + "time" + "unicode/utf8" + "unsafe" + + "github.com/aws/smithy-go" + "github.com/aws/smithy-go/document" + smithydocumentjson "github.com/aws/smithy-go/document/json" + "github.com/aws/smithy-go/encoding" + smithytime "github.com/aws/smithy-go/time" + "github.com/aws/smithy-go/traits" +) + +// Options configures JSON shape serialization and deserialization. +type Options struct { + // Controls whether the @jsonName trait is used to determine JSON object + // keys. If false (the default), the member name is used as-is. + UseJSONName bool +} + +type serCtx struct { + comma bool + inObject bool +} + +// ShapeSerializer implements marshaling of Smithy shapes to JSON. +// It writes directly to a []byte buffer without intermediate allocations. +type ShapeSerializer struct { + buf []byte + opts Options + stack []serCtx + depth int + noKey bool + initStack [64]serCtx +} + +const ( + defaultBufSize = 1024 + maxCacheableBuf = defaultBufSize * 4 +) + +var serPool = sync.Pool{ + New: func() any { + s := &ShapeSerializer{ + buf: make([]byte, 0, defaultBufSize), + } + s.stack = s.initStack[:1] + return s + }, +} + +var _ smithy.ShapeSerializer = (*ShapeSerializer)(nil) + +// NewShapeSerializer creates a new ShapeSerializer. +func NewShapeSerializer(opts ...func(*Options)) *ShapeSerializer { + o := Options{} + for _, fn := range opts { + fn(&o) + } + s := serPool.Get().(*ShapeSerializer) + s.buf = s.buf[:0] + s.opts = o + s.depth = 0 + s.noKey = false + s.stack = s.initStack[:1] + s.stack[0] = serCtx{} + return s +} + +// Close returns the serializer to the pool for reuse. +func (s *ShapeSerializer) Close() { + if cap(s.buf) > maxCacheableBuf { + s.buf = make([]byte, 0, defaultBufSize) + } + serPool.Put(s) +} + +// Bytes returns a copy of the serialized JSON bytes, safe to retain after Close(). +func (s *ShapeSerializer) Bytes() []byte { + return append([]byte(nil), s.buf...) +} + +func (s *ShapeSerializer) writeComma() { + if s.depth > 0 && s.stack[s.depth].comma { + s.buf = append(s.buf, ',') + } + s.stack[s.depth].comma = true +} + +// writePrefix handles the key-or-comma logic before a value, respecting noKey. +func (s *ShapeSerializer) writePrefix(schema *smithy.Schema) { + if s.noKey { + s.noKey = false + return + } + if schema != nil && s.depth > 0 && s.stack[s.depth].inObject { + s.writeKey(schema) + } else { + s.writeComma() + } +} + +func (s *ShapeSerializer) writeKey(schema *smithy.Schema) { + ext := getExt(schema) + if s.opts.UseJSONName { + if jk := ext.jsonNameKey; jk != nil { + if s.stack[s.depth].comma { + s.buf = append(s.buf, jk...) + } else { + s.stack[s.depth].comma = true + s.buf = append(s.buf, jk[1:]...) + } + return + } + } + jk := ext.jsonKey + if len(jk) == 0 { + s.writeComma() + return + } + if s.stack[s.depth].comma { + s.buf = append(s.buf, jk...) + } else { + s.stack[s.depth].comma = true + s.buf = append(s.buf, jk[1:]...) + } +} + +// WriteBool implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteBool(schema *smithy.Schema, v bool) { + s.writePrefix(schema) + if v { + s.buf = append(s.buf, "true"...) + } else { + s.buf = append(s.buf, "false"...) + } +} + +// WriteInt8 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt8(schema *smithy.Schema, v int8) { + s.WriteInt64(schema, int64(v)) +} + +// WriteInt16 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt16(schema *smithy.Schema, v int16) { + s.WriteInt64(schema, int64(v)) +} + +// WriteInt32 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt32(schema *smithy.Schema, v int32) { + s.WriteInt64(schema, int64(v)) +} + +// WriteInt64 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt64(schema *smithy.Schema, v int64) { + s.writePrefix(schema) + s.buf = strconv.AppendInt(s.buf, v, 10) +} + +// WriteFloat32 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteFloat32(schema *smithy.Schema, v float32) { + s.writePrefix(schema) + if math.IsInf(float64(v), 1) { + s.buf = append(s.buf, `"Infinity"`...) + } else if math.IsInf(float64(v), -1) { + s.buf = append(s.buf, `"-Infinity"`...) + } else if math.IsNaN(float64(v)) { + s.buf = append(s.buf, `"NaN"`...) + } else { + s.buf = encoding.EncodeFloat(s.buf, float64(v), 32) + } +} + +// WriteFloat64 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteFloat64(schema *smithy.Schema, v float64) { + s.writePrefix(schema) + if math.IsInf(v, 1) { + s.buf = append(s.buf, `"Infinity"`...) + } else if math.IsInf(v, -1) { + s.buf = append(s.buf, `"-Infinity"`...) + } else if math.IsNaN(v) { + s.buf = append(s.buf, `"NaN"`...) + } else { + s.buf = encoding.EncodeFloat(s.buf, v, 64) + } +} + +// WriteString implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteString(schema *smithy.Schema, v string) { + s.writePrefix(schema) + s.appendEscapedString(v) +} + +// WriteBlob implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteBlob(schema *smithy.Schema, v []byte) { + s.writePrefix(schema) + if v == nil { + s.buf = append(s.buf, "null"...) + return + } + s.buf = append(s.buf, '"') + encodedLen := base64.StdEncoding.EncodedLen(len(v)) + start := len(s.buf) + s.buf = append(s.buf, make([]byte, encodedLen)...) + base64.StdEncoding.Encode(s.buf[start:], v) + s.buf = append(s.buf, '"') +} + +// WriteList implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteList(schema *smithy.Schema) { + s.writePrefix(schema) + s.buf = append(s.buf, '[') + s.depth++; s.stack = append(s.stack, serCtx{}) +} + +// CloseList implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseList() { + s.buf = append(s.buf, ']') + s.stack = s.stack[:s.depth]; s.depth-- +} + +// WriteMap implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteMap(schema *smithy.Schema) { + s.writePrefix(schema) + s.buf = append(s.buf, '{') + s.depth++; s.stack = append(s.stack, serCtx{inObject: true}) +} + +// WriteKey implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteKey(_ *smithy.Schema, key string) { + s.writeComma() + s.appendEscapedString(key) + s.buf = append(s.buf, ':') + s.noKey = true +} + +// CloseMap implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseMap() { + s.buf = append(s.buf, '}') + s.stack = s.stack[:s.depth]; s.depth-- +} + +// WriteTime implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteTime(schema *smithy.Schema, v time.Time) { + format := "epoch-seconds" + if t, ok := smithy.SchemaTrait[*traits.TimestampFormat](schema); ok { + format = t.Format + } + + switch format { + case "date-time": + s.WriteString(schema, smithytime.FormatDateTime(v)) + case "http-date": + s.WriteString(schema, smithytime.FormatHTTPDate(v)) + default: + s.WriteFloat64(schema, smithytime.FormatEpochSeconds(v)) + } +} + +// WriteUnion implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteUnion(schema, variant *smithy.Schema) { + s.writePrefix(schema) + s.buf = append(s.buf, '{') + s.depth++; s.stack = append(s.stack, serCtx{inObject: true}) + s.writeKey(variant) + s.noKey = true +} + +// CloseUnion implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseUnion() { + s.noKey = false + s.buf = append(s.buf, '}') + s.stack = s.stack[:s.depth]; s.depth-- +} + +// WriteStruct implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteStruct(schema *smithy.Schema) { + s.writePrefix(schema) + s.buf = append(s.buf, '{') + s.depth++; s.stack = append(s.stack, serCtx{inObject: true}) +} + +// CloseStruct implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseStruct() { + s.buf = append(s.buf, '}') + s.stack = s.stack[:s.depth]; s.depth-- +} + +// WriteNil implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteNil(schema *smithy.Schema) { + s.writePrefix(schema) + s.buf = append(s.buf, "null"...) +} + +// WriteBigInt is unimplemented and will panic. +func (s *ShapeSerializer) WriteBigInt(_ *smithy.Schema, _ *big.Int) { + panic("unimplemented") +} + +// WriteBigFloat is unimplemented and will panic. +func (s *ShapeSerializer) WriteBigFloat(_ *smithy.Schema, _ *big.Float) { + panic("unimplemented") +} + +// WriteDocument writes a document value to JSON. +func (s *ShapeSerializer) WriteDocument(schema *smithy.Schema, v document.Value) { + switch vv := v.(type) { + case document.Null: + s.WriteNil(schema) + case document.Boolean: + s.WriteBool(schema, bool(vv)) + case document.Number: + s.writeRaw(schema, []byte(vv)) + case document.String: + s.WriteString(schema, string(vv)) + case document.Blob: + s.WriteBlob(schema, []byte(vv)) + case document.Timestamp: + s.WriteTime(schema, time.Time(vv)) + case document.List: + s.WriteList(schema) + for _, item := range vv { + s.WriteDocument(schema.ListMember(), item) + } + s.CloseList() + case document.Map: + s.WriteMap(schema) + for k, item := range vv { + s.WriteKey(schema.MapKey(), k) + s.WriteDocument(schema.MapValue(), item) + } + s.CloseMap() + case document.Structure: + s.WriteMap(schema) + for k, item := range vv.Members { + s.WriteKey(nil, k) + s.WriteDocument(nil, item) + } + s.CloseMap() + case document.Opaque: + s.writeOpaqueDocument(schema, vv.Value) + case *document.Opaque: + s.writeOpaqueDocument(schema, vv.Value) + } +} + +func (s *ShapeSerializer) writeOpaqueDocument(schema *smithy.Schema, v any) { + if m, ok := v.(document.Marshaler); ok { + p, _ := m.MarshalSmithyDocument() + s.writeRaw(schema, p) + return + } + denc := smithydocumentjson.NewEncoder() + p, _ := denc.Encode(v) + s.writeRaw(schema, p) +} + +func (s *ShapeSerializer) writeRaw(schema *smithy.Schema, p []byte) { + s.writePrefix(schema) + s.buf = append(s.buf, p...) +} + +// jsonMemberName returns the JSON key for a schema member. +func (s *ShapeSerializer) jsonMemberName(schema *smithy.Schema) string { + if s.opts.UseJSONName { + if jn, ok := smithy.SchemaTrait[*traits.JSONName](schema); ok { + return jn.Name + } + } + return schema.MemberName() +} + +// appendEscapedString writes a JSON-escaped string to the buffer. +func (s *ShapeSerializer) appendEscapedString(v string) { + s.buf = append(s.buf, '"') + + // fast path: SWAR check if entire string is safe ASCII + i := 0 + p := unsafe.StringData(v) + for i+8 <= len(v) { + w := *(*uint64)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + uintptr(i))) + // high bit set means >= 0x80, hasLess detects control chars, + // hasValue detects '"', '\\', and DEL (0x7F) + if w&hi|hasLess(w, 0x20)|hasValue(w, '"')|hasValue(w, '\\')|hasValue(w, 0x7F) != 0 { + break + } + i += 8 + } + for ; i < len(v); i++ { + if v[i] >= utf8.RuneSelf || !safeSet[v[i]] { + break + } + } + if i == len(v) { + s.buf = append(s.buf, v...) + s.buf = append(s.buf, '"') + return + } + + // write the safe prefix we already validated, escape from i onward + s.buf = append(s.buf, v[:i]...) + start := i + for i < len(v) { + b := v[i] + if b < utf8.RuneSelf { + if safeSet[b] { + i++ + continue + } + if start < i { + s.buf = append(s.buf, v[start:i]...) + } + switch b { + case '\\', '"': + s.buf = append(s.buf, '\\', b) + case '\n': + s.buf = append(s.buf, '\\', 'n') + case '\r': + s.buf = append(s.buf, '\\', 'r') + case '\t': + s.buf = append(s.buf, '\\', 't') + default: + s.buf = append(s.buf, '\\', 'u', '0', '0', hex[b>>4], hex[b&0xF]) + } + i++ + start = i + continue + } + c, size := utf8.DecodeRuneInString(v[i:]) + if c == utf8.RuneError && size == 1 { + if start < i { + s.buf = append(s.buf, v[start:i]...) + } + s.buf = append(s.buf, `�`...) + i += size + start = i + continue + } + if c == '
' || c == '
' { + if start < i { + s.buf = append(s.buf, v[start:i]...) + } + s.buf = append(s.buf, '\\', 'u', '2', '0', '2', hex[c&0xF]) + i += size + start = i + continue + } + i += size + } + if start < len(v) { + s.buf = append(s.buf, v[start:]...) + } + s.buf = append(s.buf, '"') +} + +var safeSet = [utf8.RuneSelf]bool{ + ' ': true, + '!': true, + '"': false, + '#': true, + '$': true, + '%': true, + '&': true, + '\'': true, + '(': true, + ')': true, + '*': true, + '+': true, + ',': true, + '-': true, + '.': true, + '/': true, + '0': true, + '1': true, + '2': true, + '3': true, + '4': true, + '5': true, + '6': true, + '7': true, + '8': true, + '9': true, + ':': true, + ';': true, + '<': true, + '=': true, + '>': true, + '?': true, + '@': true, + 'A': true, + 'B': true, + 'C': true, + 'D': true, + 'E': true, + 'F': true, + 'G': true, + 'H': true, + 'I': true, + 'J': true, + 'K': true, + 'L': true, + 'M': true, + 'N': true, + 'O': true, + 'P': true, + 'Q': true, + 'R': true, + 'S': true, + 'T': true, + 'U': true, + 'V': true, + 'W': true, + 'X': true, + 'Y': true, + 'Z': true, + '[': true, + '\\': false, + ']': true, + '^': true, + '_': true, + '`': true, + 'a': true, + 'b': true, + 'c': true, + 'd': true, + 'e': true, + 'f': true, + 'g': true, + 'h': true, + 'i': true, + 'j': true, + 'k': true, + 'l': true, + 'm': true, + 'n': true, + 'o': true, + 'p': true, + 'q': true, + 'r': true, + 's': true, + 't': true, + 'u': true, + 'v': true, + 'w': true, + 'x': true, + 'y': true, + 'z': true, + '{': true, + '|': true, + '}': true, + '~': true, + '': false, +} + +var hex = "0123456789abcdef" diff --git a/transport/http/protocol/internal/json/stack.go b/transport/http/protocol/internal/json/stack.go new file mode 100644 index 000000000..8ee85b6be --- /dev/null +++ b/transport/http/protocol/internal/json/stack.go @@ -0,0 +1,38 @@ +package json + +// chosen as an arbitrary average max depth for the RPC-style payloads we +// typically deal with in an SDK client +const initialCap = 8 + +type stackT[T any] struct { + values []T + sentinel T +} + +func newStackT[T any](sentinel T) stackT[T] { + return stackT[T]{ + values: make([]T, 0, initialCap), + sentinel: sentinel, + } +} + +func (s *stackT[T]) Push(v T) { + s.values = append(s.values, v) +} + +func (s *stackT[T]) Pop() T { + if len(s.values) == 0 { + return s.sentinel + } + idx := len(s.values) - 1 + v := s.values[idx] + s.values = s.values[:idx] + return v +} + +func (s *stackT[T]) Top() T { + if len(s.values) == 0 { + return s.sentinel + } + return s.values[len(s.values)-1] +} diff --git a/transport/http/protocol/internal/json/testdata/README.md b/transport/http/protocol/internal/json/testdata/README.md new file mode 100644 index 000000000..a1eb19619 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/README.md @@ -0,0 +1 @@ +Test cases sourced from [nst/JSONTestSuite](https://github.com/nst/JSONTestSuite). diff --git a/transport/http/protocol/internal/json/testdata/fuzz/FuzzParserDifferential/8a874704f001c1c7 b/transport/http/protocol/internal/json/testdata/fuzz/FuzzParserDifferential/8a874704f001c1c7 new file mode 100644 index 000000000..ef0d31e1c --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/fuzz/FuzzParserDifferential/8a874704f001c1c7 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("0A0") diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_number_double_huge_neg_exp.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_number_double_huge_neg_exp.json new file mode 100644 index 000000000..ae4c7b71f --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_number_double_huge_neg_exp.json @@ -0,0 +1 @@ +[123.456e-789] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_number_huge_exp.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_number_huge_exp.json new file mode 100644 index 000000000..9b5efa236 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_number_huge_exp.json @@ -0,0 +1 @@ +[0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_number_neg_int_huge_exp.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_number_neg_int_huge_exp.json new file mode 100755 index 000000000..3abd58a5c --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_number_neg_int_huge_exp.json @@ -0,0 +1 @@ +[-1e+9999] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_number_pos_double_huge_exp.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_number_pos_double_huge_exp.json new file mode 100755 index 000000000..e10a7eb62 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_number_pos_double_huge_exp.json @@ -0,0 +1 @@ +[1.5e+9999] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_number_real_neg_overflow.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_number_real_neg_overflow.json new file mode 100644 index 000000000..3d628a994 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_number_real_neg_overflow.json @@ -0,0 +1 @@ +[-123123e100000] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_number_real_pos_overflow.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_number_real_pos_overflow.json new file mode 100644 index 000000000..54d7d3dcd --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_number_real_pos_overflow.json @@ -0,0 +1 @@ +[123123e100000] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_number_real_underflow.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_number_real_underflow.json new file mode 100644 index 000000000..c5236eb26 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_number_real_underflow.json @@ -0,0 +1 @@ +[123e-10000000] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_number_too_big_neg_int.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_number_too_big_neg_int.json new file mode 100644 index 000000000..dfa384619 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_number_too_big_neg_int.json @@ -0,0 +1 @@ +[-123123123123123123123123123123] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_number_too_big_pos_int.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_number_too_big_pos_int.json new file mode 100644 index 000000000..338a8c3c0 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_number_too_big_pos_int.json @@ -0,0 +1 @@ +[100000000000000000000] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_number_very_big_negative_int.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_number_very_big_negative_int.json new file mode 100755 index 000000000..e2d9738c2 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_number_very_big_negative_int.json @@ -0,0 +1 @@ +[-237462374673276894279832749832423479823246327846] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_object_key_lone_2nd_surrogate.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_object_key_lone_2nd_surrogate.json new file mode 100644 index 000000000..5be7ebaf9 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_object_key_lone_2nd_surrogate.json @@ -0,0 +1 @@ +{"\uDFAA":0} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_1st_surrogate_but_2nd_missing.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_1st_surrogate_but_2nd_missing.json new file mode 100644 index 000000000..3b9e37c67 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_1st_surrogate_but_2nd_missing.json @@ -0,0 +1 @@ +["\uDADA"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_1st_valid_surrogate_2nd_invalid.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_1st_valid_surrogate_2nd_invalid.json new file mode 100644 index 000000000..487592832 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_1st_valid_surrogate_2nd_invalid.json @@ -0,0 +1 @@ +["\uD888\u1234"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_UTF-16LE_with_BOM.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_UTF-16LE_with_BOM.json new file mode 100644 index 000000000..2a79c0629 Binary files /dev/null and b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_UTF-16LE_with_BOM.json differ diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_UTF-8_invalid_sequence.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_UTF-8_invalid_sequence.json new file mode 100755 index 000000000..e2a968a15 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_UTF-8_invalid_sequence.json @@ -0,0 +1 @@ +["日ш"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_UTF8_surrogate_U+D800.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_UTF8_surrogate_U+D800.json new file mode 100644 index 000000000..916bff920 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_UTF8_surrogate_U+D800.json @@ -0,0 +1 @@ +[""] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_incomplete_surrogate_and_escape_valid.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_incomplete_surrogate_and_escape_valid.json new file mode 100755 index 000000000..3cb11d229 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_incomplete_surrogate_and_escape_valid.json @@ -0,0 +1 @@ +["\uD800\n"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_incomplete_surrogate_pair.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_incomplete_surrogate_pair.json new file mode 100755 index 000000000..38ec23bb0 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_incomplete_surrogate_pair.json @@ -0,0 +1 @@ +["\uDd1ea"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_incomplete_surrogates_escape_valid.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_incomplete_surrogates_escape_valid.json new file mode 100755 index 000000000..c9cd6f6c3 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_incomplete_surrogates_escape_valid.json @@ -0,0 +1 @@ +["\uD800\uD800\n"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_invalid_lonely_surrogate.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_invalid_lonely_surrogate.json new file mode 100755 index 000000000..3abbd8d8d --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_invalid_lonely_surrogate.json @@ -0,0 +1 @@ +["\ud800"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_invalid_surrogate.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_invalid_surrogate.json new file mode 100755 index 000000000..ffddc04f5 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_invalid_surrogate.json @@ -0,0 +1 @@ +["\ud800abc"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_invalid_utf-8.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_invalid_utf-8.json new file mode 100644 index 000000000..8e45a7eca --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_invalid_utf-8.json @@ -0,0 +1 @@ +[""] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_inverted_surrogates_U+1D11E.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_inverted_surrogates_U+1D11E.json new file mode 100755 index 000000000..0d5456cc3 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_inverted_surrogates_U+1D11E.json @@ -0,0 +1 @@ +["\uDd1e\uD834"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_iso_latin_1.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_iso_latin_1.json new file mode 100644 index 000000000..9389c9823 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_iso_latin_1.json @@ -0,0 +1 @@ +[""] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_lone_second_surrogate.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_lone_second_surrogate.json new file mode 100644 index 000000000..1dbd397f3 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_lone_second_surrogate.json @@ -0,0 +1 @@ +["\uDFAA"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_lone_utf8_continuation_byte.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_lone_utf8_continuation_byte.json new file mode 100644 index 000000000..729337c0a --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_lone_utf8_continuation_byte.json @@ -0,0 +1 @@ +[""] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_not_in_unicode_range.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_not_in_unicode_range.json new file mode 100644 index 000000000..df90a2916 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_not_in_unicode_range.json @@ -0,0 +1 @@ +[""] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_overlong_sequence_2_bytes.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_overlong_sequence_2_bytes.json new file mode 100644 index 000000000..c8cee5e0a --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_overlong_sequence_2_bytes.json @@ -0,0 +1 @@ +[""] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_overlong_sequence_6_bytes.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_overlong_sequence_6_bytes.json new file mode 100755 index 000000000..9a91da791 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_overlong_sequence_6_bytes.json @@ -0,0 +1 @@ +[""] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_overlong_sequence_6_bytes_null.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_overlong_sequence_6_bytes_null.json new file mode 100755 index 000000000..d24fffdd9 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_overlong_sequence_6_bytes_null.json @@ -0,0 +1 @@ +[""] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_truncated-utf-8.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_truncated-utf-8.json new file mode 100644 index 000000000..63c7777fb --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_truncated-utf-8.json @@ -0,0 +1 @@ +[""] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_utf16BE_no_BOM.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_utf16BE_no_BOM.json new file mode 100644 index 000000000..57e5392ff Binary files /dev/null and b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_utf16BE_no_BOM.json differ diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_string_utf16LE_no_BOM.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_utf16LE_no_BOM.json new file mode 100644 index 000000000..c49c1b25d Binary files /dev/null and b/transport/http/protocol/internal/json/testdata/test_parsing/i_string_utf16LE_no_BOM.json differ diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_structure_500_nested_arrays.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_structure_500_nested_arrays.json new file mode 100644 index 000000000..711840589 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_structure_500_nested_arrays.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/i_structure_UTF-8_BOM_empty_object.json b/transport/http/protocol/internal/json/testdata/test_parsing/i_structure_UTF-8_BOM_empty_object.json new file mode 100755 index 000000000..22fdca1b2 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/i_structure_UTF-8_BOM_empty_object.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_1_true_without_comma.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_1_true_without_comma.json new file mode 100644 index 000000000..c14e3f6b1 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_1_true_without_comma.json @@ -0,0 +1 @@ +[1 true] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_a_invalid_utf8.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_a_invalid_utf8.json new file mode 100644 index 000000000..38a86e2e6 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_a_invalid_utf8.json @@ -0,0 +1 @@ +[a] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_colon_instead_of_comma.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_colon_instead_of_comma.json new file mode 100644 index 000000000..0d02ad448 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_colon_instead_of_comma.json @@ -0,0 +1 @@ +["": 1] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_comma_after_close.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_comma_after_close.json new file mode 100644 index 000000000..2ccba8d95 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_comma_after_close.json @@ -0,0 +1 @@ +[""], \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_comma_and_number.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_comma_and_number.json new file mode 100755 index 000000000..d2c84e374 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_comma_and_number.json @@ -0,0 +1 @@ +[,1] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_double_comma.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_double_comma.json new file mode 100755 index 000000000..0431712bc --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_double_comma.json @@ -0,0 +1 @@ +[1,,2] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_double_extra_comma.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_double_extra_comma.json new file mode 100644 index 000000000..3f01d3129 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_double_extra_comma.json @@ -0,0 +1 @@ +["x",,] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_extra_close.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_extra_close.json new file mode 100644 index 000000000..c12f9fae1 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_extra_close.json @@ -0,0 +1 @@ +["x"]] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_extra_comma.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_extra_comma.json new file mode 100644 index 000000000..5f8ce18e4 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_extra_comma.json @@ -0,0 +1 @@ +["",] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_incomplete.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_incomplete.json new file mode 100644 index 000000000..cc65b0b51 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_incomplete.json @@ -0,0 +1 @@ +["x" \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_incomplete_invalid_value.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_incomplete_invalid_value.json new file mode 100644 index 000000000..c21a8f6cf --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_incomplete_invalid_value.json @@ -0,0 +1 @@ +[x \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_inner_array_no_comma.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_inner_array_no_comma.json new file mode 100644 index 000000000..c70b71647 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_inner_array_no_comma.json @@ -0,0 +1 @@ +[3[4]] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_invalid_utf8.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_invalid_utf8.json new file mode 100644 index 000000000..6099d3441 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_invalid_utf8.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_items_separated_by_semicolon.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_items_separated_by_semicolon.json new file mode 100755 index 000000000..d4bd7314c --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_items_separated_by_semicolon.json @@ -0,0 +1 @@ +[1:2] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_just_comma.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_just_comma.json new file mode 100755 index 000000000..9d7077c68 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_just_comma.json @@ -0,0 +1 @@ +[,] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_just_minus.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_just_minus.json new file mode 100755 index 000000000..29501c6ca --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_just_minus.json @@ -0,0 +1 @@ +[-] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_missing_value.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_missing_value.json new file mode 100644 index 000000000..3a6ba86f3 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_missing_value.json @@ -0,0 +1 @@ +[ , ""] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_newlines_unclosed.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_newlines_unclosed.json new file mode 100644 index 000000000..646680065 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_newlines_unclosed.json @@ -0,0 +1,3 @@ +["a", +4 +,1, \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_number_and_comma.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_number_and_comma.json new file mode 100755 index 000000000..13f6f1d18 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_number_and_comma.json @@ -0,0 +1 @@ +[1,] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_number_and_several_commas.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_number_and_several_commas.json new file mode 100755 index 000000000..0ac408cb8 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_number_and_several_commas.json @@ -0,0 +1 @@ +[1,,] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_spaces_vertical_tab_formfeed.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_spaces_vertical_tab_formfeed.json new file mode 100755 index 000000000..6cd7cf585 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_spaces_vertical_tab_formfeed.json @@ -0,0 +1 @@ +[" a"\f] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_star_inside.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_star_inside.json new file mode 100755 index 000000000..5a5194647 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_star_inside.json @@ -0,0 +1 @@ +[*] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_unclosed.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_unclosed.json new file mode 100644 index 000000000..060733059 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_unclosed.json @@ -0,0 +1 @@ +["" \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_unclosed_trailing_comma.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_unclosed_trailing_comma.json new file mode 100644 index 000000000..6604698ff --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_unclosed_trailing_comma.json @@ -0,0 +1 @@ +[1, \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_unclosed_with_new_lines.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_unclosed_with_new_lines.json new file mode 100644 index 000000000..4f61de3fb --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_unclosed_with_new_lines.json @@ -0,0 +1,3 @@ +[1, +1 +,1 \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_array_unclosed_with_object_inside.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_unclosed_with_object_inside.json new file mode 100644 index 000000000..043a87e2d --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_array_unclosed_with_object_inside.json @@ -0,0 +1 @@ +[{} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_incomplete_false.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_incomplete_false.json new file mode 100644 index 000000000..eb18c6a14 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_incomplete_false.json @@ -0,0 +1 @@ +[fals] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_incomplete_null.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_incomplete_null.json new file mode 100644 index 000000000..c18ef5385 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_incomplete_null.json @@ -0,0 +1 @@ +[nul] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_incomplete_true.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_incomplete_true.json new file mode 100644 index 000000000..f451ac6d2 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_incomplete_true.json @@ -0,0 +1 @@ +[tru] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_multidigit_number_then_00.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_multidigit_number_then_00.json new file mode 100644 index 000000000..c22507b86 Binary files /dev/null and b/transport/http/protocol/internal/json/testdata/test_parsing/n_multidigit_number_then_00.json differ diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_++.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_++.json new file mode 100644 index 000000000..bdb62aaf4 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_++.json @@ -0,0 +1 @@ +[++1234] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_+1.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_+1.json new file mode 100755 index 000000000..3cbe58c92 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_+1.json @@ -0,0 +1 @@ +[+1] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_+Inf.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_+Inf.json new file mode 100755 index 000000000..871ae14d5 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_+Inf.json @@ -0,0 +1 @@ +[+Inf] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_-01.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_-01.json new file mode 100755 index 000000000..0df32bac8 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_-01.json @@ -0,0 +1 @@ +[-01] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_-1.0..json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_-1.0..json new file mode 100755 index 000000000..7cf55a85a --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_-1.0..json @@ -0,0 +1 @@ +[-1.0.] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_-2..json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_-2..json new file mode 100755 index 000000000..9be84365d --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_-2..json @@ -0,0 +1 @@ +[-2.] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_-NaN.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_-NaN.json new file mode 100755 index 000000000..f61615d40 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_-NaN.json @@ -0,0 +1 @@ +[-NaN] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_.-1.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_.-1.json new file mode 100644 index 000000000..1c9f2dd1b --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_.-1.json @@ -0,0 +1 @@ +[.-1] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_.2e-3.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_.2e-3.json new file mode 100755 index 000000000..c6c976f25 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_.2e-3.json @@ -0,0 +1 @@ +[.2e-3] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0.1.2.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0.1.2.json new file mode 100755 index 000000000..c83a25621 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0.1.2.json @@ -0,0 +1 @@ +[0.1.2] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0.3e+.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0.3e+.json new file mode 100644 index 000000000..a55a1bfef --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0.3e+.json @@ -0,0 +1 @@ +[0.3e+] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0.3e.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0.3e.json new file mode 100644 index 000000000..3dd5df4b3 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0.3e.json @@ -0,0 +1 @@ +[0.3e] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0.e1.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0.e1.json new file mode 100644 index 000000000..c92c71ccb --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0.e1.json @@ -0,0 +1 @@ +[0.e1] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0_capital_E+.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0_capital_E+.json new file mode 100644 index 000000000..3ba2c7d6d --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0_capital_E+.json @@ -0,0 +1 @@ +[0E+] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0_capital_E.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0_capital_E.json new file mode 100755 index 000000000..5301840d1 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0_capital_E.json @@ -0,0 +1 @@ +[0E] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0e+.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0e+.json new file mode 100644 index 000000000..8ab0bc4b8 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0e+.json @@ -0,0 +1 @@ +[0e+] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0e.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0e.json new file mode 100644 index 000000000..47ec421bb --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_0e.json @@ -0,0 +1 @@ +[0e] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_1.0e+.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_1.0e+.json new file mode 100755 index 000000000..cd84b9f69 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_1.0e+.json @@ -0,0 +1 @@ +[1.0e+] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_1.0e-.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_1.0e-.json new file mode 100755 index 000000000..4eb7afa0f --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_1.0e-.json @@ -0,0 +1 @@ +[1.0e-] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_1.0e.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_1.0e.json new file mode 100755 index 000000000..21753f4c7 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_1.0e.json @@ -0,0 +1 @@ +[1.0e] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_1_000.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_1_000.json new file mode 100755 index 000000000..7b18b66b3 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_1_000.json @@ -0,0 +1 @@ +[1 000.0] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_1eE2.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_1eE2.json new file mode 100755 index 000000000..4318a341d --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_1eE2.json @@ -0,0 +1 @@ +[1eE2] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_2.e+3.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_2.e+3.json new file mode 100755 index 000000000..4442f394d --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_2.e+3.json @@ -0,0 +1 @@ +[2.e+3] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_2.e-3.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_2.e-3.json new file mode 100755 index 000000000..a65060edf --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_2.e-3.json @@ -0,0 +1 @@ +[2.e-3] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_2.e3.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_2.e3.json new file mode 100755 index 000000000..66f7cf701 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_2.e3.json @@ -0,0 +1 @@ +[2.e3] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_9.e+.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_9.e+.json new file mode 100644 index 000000000..732a7b11c --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_9.e+.json @@ -0,0 +1 @@ +[9.e+] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_Inf.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_Inf.json new file mode 100755 index 000000000..c40c734c3 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_Inf.json @@ -0,0 +1 @@ +[Inf] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_NaN.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_NaN.json new file mode 100755 index 000000000..499231790 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_NaN.json @@ -0,0 +1 @@ +[NaN] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_U+FF11_fullwidth_digit_one.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_U+FF11_fullwidth_digit_one.json new file mode 100644 index 000000000..b14587e5e --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_U+FF11_fullwidth_digit_one.json @@ -0,0 +1 @@ +[1] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_expression.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_expression.json new file mode 100644 index 000000000..76fdbc8a4 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_expression.json @@ -0,0 +1 @@ +[1+2] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_hex_1_digit.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_hex_1_digit.json new file mode 100644 index 000000000..3b214880c --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_hex_1_digit.json @@ -0,0 +1 @@ +[0x1] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_hex_2_digits.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_hex_2_digits.json new file mode 100644 index 000000000..83e516ab0 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_hex_2_digits.json @@ -0,0 +1 @@ +[0x42] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_infinity.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_infinity.json new file mode 100755 index 000000000..8c2baf783 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_infinity.json @@ -0,0 +1 @@ +[Infinity] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_invalid+-.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_invalid+-.json new file mode 100644 index 000000000..1cce602b5 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_invalid+-.json @@ -0,0 +1 @@ +[0e+-1] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_invalid-negative-real.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_invalid-negative-real.json new file mode 100644 index 000000000..5fc3c1efb --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_invalid-negative-real.json @@ -0,0 +1 @@ +[-123.123foo] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_invalid-utf-8-in-bigger-int.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_invalid-utf-8-in-bigger-int.json new file mode 100644 index 000000000..3b97e580e --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_invalid-utf-8-in-bigger-int.json @@ -0,0 +1 @@ +[123] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_invalid-utf-8-in-exponent.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_invalid-utf-8-in-exponent.json new file mode 100644 index 000000000..ea35d723c --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_invalid-utf-8-in-exponent.json @@ -0,0 +1 @@ +[1e1] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_invalid-utf-8-in-int.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_invalid-utf-8-in-int.json new file mode 100644 index 000000000..371226e4c --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_invalid-utf-8-in-int.json @@ -0,0 +1 @@ +[0] diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_minus_infinity.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_minus_infinity.json new file mode 100755 index 000000000..cf4133d22 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_minus_infinity.json @@ -0,0 +1 @@ +[-Infinity] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_minus_sign_with_trailing_garbage.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_minus_sign_with_trailing_garbage.json new file mode 100644 index 000000000..a6d8e78e7 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_minus_sign_with_trailing_garbage.json @@ -0,0 +1 @@ +[-foo] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_minus_space_1.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_minus_space_1.json new file mode 100644 index 000000000..9a5ebedf6 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_minus_space_1.json @@ -0,0 +1 @@ +[- 1] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_neg_int_starting_with_zero.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_neg_int_starting_with_zero.json new file mode 100644 index 000000000..67af0960a --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_neg_int_starting_with_zero.json @@ -0,0 +1 @@ +[-012] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_neg_real_without_int_part.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_neg_real_without_int_part.json new file mode 100755 index 000000000..1f2a43496 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_neg_real_without_int_part.json @@ -0,0 +1 @@ +[-.123] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_neg_with_garbage_at_end.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_neg_with_garbage_at_end.json new file mode 100644 index 000000000..2aa73119f --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_neg_with_garbage_at_end.json @@ -0,0 +1 @@ +[-1x] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_real_garbage_after_e.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_real_garbage_after_e.json new file mode 100644 index 000000000..9213dfca8 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_real_garbage_after_e.json @@ -0,0 +1 @@ +[1ea] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_real_with_invalid_utf8_after_e.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_real_with_invalid_utf8_after_e.json new file mode 100644 index 000000000..1e52ef964 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_real_with_invalid_utf8_after_e.json @@ -0,0 +1 @@ +[1e] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_real_without_fractional_part.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_real_without_fractional_part.json new file mode 100755 index 000000000..1de287cf8 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_real_without_fractional_part.json @@ -0,0 +1 @@ +[1.] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_starting_with_dot.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_starting_with_dot.json new file mode 100755 index 000000000..f682dbdce --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_starting_with_dot.json @@ -0,0 +1 @@ +[.123] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_with_alpha.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_with_alpha.json new file mode 100644 index 000000000..1e42d8182 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_with_alpha.json @@ -0,0 +1 @@ +[1.2a-3] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_with_alpha_char.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_with_alpha_char.json new file mode 100644 index 000000000..b79daccb8 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_with_alpha_char.json @@ -0,0 +1 @@ +[1.8011670033376514H-308] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_number_with_leading_zero.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_with_leading_zero.json new file mode 100755 index 000000000..7106da1f3 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_number_with_leading_zero.json @@ -0,0 +1 @@ +[012] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_bad_value.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_bad_value.json new file mode 100644 index 000000000..a03a8c03b --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_bad_value.json @@ -0,0 +1 @@ +["x", truth] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_bracket_key.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_bracket_key.json new file mode 100644 index 000000000..cc443b483 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_bracket_key.json @@ -0,0 +1 @@ +{[: "x"} diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_comma_instead_of_colon.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_comma_instead_of_colon.json new file mode 100644 index 000000000..8d5637708 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_comma_instead_of_colon.json @@ -0,0 +1 @@ +{"x", null} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_double_colon.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_double_colon.json new file mode 100644 index 000000000..80e8c7b89 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_double_colon.json @@ -0,0 +1 @@ +{"x"::"b"} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_emoji.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_emoji.json new file mode 100644 index 000000000..cb4078eaa --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_emoji.json @@ -0,0 +1 @@ +{🇨🇭} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_garbage_at_end.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_garbage_at_end.json new file mode 100644 index 000000000..80c42cbad --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_garbage_at_end.json @@ -0,0 +1 @@ +{"a":"a" 123} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_key_with_single_quotes.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_key_with_single_quotes.json new file mode 100755 index 000000000..77c327599 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_key_with_single_quotes.json @@ -0,0 +1 @@ +{key: 'value'} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_lone_continuation_byte_in_key_and_trailing_comma.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_lone_continuation_byte_in_key_and_trailing_comma.json new file mode 100644 index 000000000..aa2cb637c --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_lone_continuation_byte_in_key_and_trailing_comma.json @@ -0,0 +1 @@ +{"":"0",} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_missing_colon.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_missing_colon.json new file mode 100644 index 000000000..b98eff62d --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_missing_colon.json @@ -0,0 +1 @@ +{"a" b} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_missing_key.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_missing_key.json new file mode 100755 index 000000000..b4fb0f528 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_missing_key.json @@ -0,0 +1 @@ +{:"b"} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_missing_semicolon.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_missing_semicolon.json new file mode 100755 index 000000000..e3451384f --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_missing_semicolon.json @@ -0,0 +1 @@ +{"a" "b"} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_missing_value.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_missing_value.json new file mode 100644 index 000000000..3ef538a60 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_missing_value.json @@ -0,0 +1 @@ +{"a": \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_no-colon.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_no-colon.json new file mode 100644 index 000000000..f3797b357 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_no-colon.json @@ -0,0 +1 @@ +{"a" \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_non_string_key.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_non_string_key.json new file mode 100755 index 000000000..b9945b34b --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_non_string_key.json @@ -0,0 +1 @@ +{1:1} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_non_string_key_but_huge_number_instead.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_non_string_key_but_huge_number_instead.json new file mode 100755 index 000000000..b37fa86c0 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_non_string_key_but_huge_number_instead.json @@ -0,0 +1 @@ +{9999E9999:1} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_repeated_null_null.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_repeated_null_null.json new file mode 100755 index 000000000..f7d2959d0 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_repeated_null_null.json @@ -0,0 +1 @@ +{null:null,null:null} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_several_trailing_commas.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_several_trailing_commas.json new file mode 100755 index 000000000..3c9afe8dc --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_several_trailing_commas.json @@ -0,0 +1 @@ +{"id":0,,,,,} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_single_quote.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_single_quote.json new file mode 100644 index 000000000..e5cdf976a --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_single_quote.json @@ -0,0 +1 @@ +{'a':0} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_trailing_comma.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_trailing_comma.json new file mode 100755 index 000000000..a4b025094 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_trailing_comma.json @@ -0,0 +1 @@ +{"id":0,} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_trailing_comment.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_trailing_comment.json new file mode 100644 index 000000000..a372c6553 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_trailing_comment.json @@ -0,0 +1 @@ +{"a":"b"}/**/ \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_trailing_comment_open.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_trailing_comment_open.json new file mode 100644 index 000000000..d557f41ca --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_trailing_comment_open.json @@ -0,0 +1 @@ +{"a":"b"}/**// \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_trailing_comment_slash_open.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_trailing_comment_slash_open.json new file mode 100644 index 000000000..e335136c0 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_trailing_comment_slash_open.json @@ -0,0 +1 @@ +{"a":"b"}// \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_trailing_comment_slash_open_incomplete.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_trailing_comment_slash_open_incomplete.json new file mode 100644 index 000000000..d892e49f1 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_trailing_comment_slash_open_incomplete.json @@ -0,0 +1 @@ +{"a":"b"}/ \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_two_commas_in_a_row.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_two_commas_in_a_row.json new file mode 100755 index 000000000..7c639ae64 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_two_commas_in_a_row.json @@ -0,0 +1 @@ +{"a":"b",,"c":"d"} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_unquoted_key.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_unquoted_key.json new file mode 100644 index 000000000..8ba137293 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_unquoted_key.json @@ -0,0 +1 @@ +{a: "b"} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_unterminated-value.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_unterminated-value.json new file mode 100644 index 000000000..7fe699a6a --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_unterminated-value.json @@ -0,0 +1 @@ +{"a":"a \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_with_single_string.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_with_single_string.json new file mode 100644 index 000000000..d63f7fbb7 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_with_single_string.json @@ -0,0 +1 @@ +{ "foo" : "bar", "a" } \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_object_with_trailing_garbage.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_with_trailing_garbage.json new file mode 100644 index 000000000..787c8f0a8 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_object_with_trailing_garbage.json @@ -0,0 +1 @@ +{"a":"b"}# \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_single_space.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_single_space.json new file mode 100755 index 000000000..0519ecba6 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_single_space.json @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_1_surrogate_then_escape.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_1_surrogate_then_escape.json new file mode 100644 index 000000000..acec66d8f --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_1_surrogate_then_escape.json @@ -0,0 +1 @@ +["\uD800\"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_1_surrogate_then_escape_u.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_1_surrogate_then_escape_u.json new file mode 100644 index 000000000..e834b05e9 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_1_surrogate_then_escape_u.json @@ -0,0 +1 @@ +["\uD800\u"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_1_surrogate_then_escape_u1.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_1_surrogate_then_escape_u1.json new file mode 100644 index 000000000..a04cd3489 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_1_surrogate_then_escape_u1.json @@ -0,0 +1 @@ +["\uD800\u1"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_1_surrogate_then_escape_u1x.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_1_surrogate_then_escape_u1x.json new file mode 100644 index 000000000..bfbd23409 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_1_surrogate_then_escape_u1x.json @@ -0,0 +1 @@ +["\uD800\u1x"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_accentuated_char_no_quotes.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_accentuated_char_no_quotes.json new file mode 100644 index 000000000..fd6895693 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_accentuated_char_no_quotes.json @@ -0,0 +1 @@ +[é] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_backslash_00.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_backslash_00.json new file mode 100644 index 000000000..b5bf267b5 Binary files /dev/null and b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_backslash_00.json differ diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_escape_x.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_escape_x.json new file mode 100644 index 000000000..fae291938 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_escape_x.json @@ -0,0 +1 @@ +["\x00"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_escaped_backslash_bad.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_escaped_backslash_bad.json new file mode 100755 index 000000000..016fcb47e --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_escaped_backslash_bad.json @@ -0,0 +1 @@ +["\\\"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_escaped_ctrl_char_tab.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_escaped_ctrl_char_tab.json new file mode 100644 index 000000000..f35ea382b --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_escaped_ctrl_char_tab.json @@ -0,0 +1 @@ +["\ "] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_escaped_emoji.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_escaped_emoji.json new file mode 100644 index 000000000..a27775421 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_escaped_emoji.json @@ -0,0 +1 @@ +["\🌀"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_incomplete_escape.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_incomplete_escape.json new file mode 100755 index 000000000..3415c33ca --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_incomplete_escape.json @@ -0,0 +1 @@ +["\"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_incomplete_escaped_character.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_incomplete_escaped_character.json new file mode 100755 index 000000000..0f2197ea2 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_incomplete_escaped_character.json @@ -0,0 +1 @@ +["\u00A"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_incomplete_surrogate.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_incomplete_surrogate.json new file mode 100755 index 000000000..75504a656 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_incomplete_surrogate.json @@ -0,0 +1 @@ +["\uD834\uDd"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_incomplete_surrogate_escape_invalid.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_incomplete_surrogate_escape_invalid.json new file mode 100755 index 000000000..bd9656060 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_incomplete_surrogate_escape_invalid.json @@ -0,0 +1 @@ +["\uD800\uD800\x"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_invalid-utf-8-in-escape.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_invalid-utf-8-in-escape.json new file mode 100644 index 000000000..0c4300643 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_invalid-utf-8-in-escape.json @@ -0,0 +1 @@ +["\u"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_invalid_backslash_esc.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_invalid_backslash_esc.json new file mode 100755 index 000000000..d1eb60921 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_invalid_backslash_esc.json @@ -0,0 +1 @@ +["\a"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_invalid_unicode_escape.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_invalid_unicode_escape.json new file mode 100644 index 000000000..7608cb6ba --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_invalid_unicode_escape.json @@ -0,0 +1 @@ +["\uqqqq"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_invalid_utf8_after_escape.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_invalid_utf8_after_escape.json new file mode 100644 index 000000000..2f757a25b --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_invalid_utf8_after_escape.json @@ -0,0 +1 @@ +["\"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_leading_uescaped_thinspace.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_leading_uescaped_thinspace.json new file mode 100755 index 000000000..7b297c636 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_leading_uescaped_thinspace.json @@ -0,0 +1 @@ +[\u0020"asd"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_no_quotes_with_bad_escape.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_no_quotes_with_bad_escape.json new file mode 100644 index 000000000..01bc70aba --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_no_quotes_with_bad_escape.json @@ -0,0 +1 @@ +[\n] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_single_doublequote.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_single_doublequote.json new file mode 100755 index 000000000..9d68933c4 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_single_doublequote.json @@ -0,0 +1 @@ +" \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_single_quote.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_single_quote.json new file mode 100644 index 000000000..caff239bf --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_single_quote.json @@ -0,0 +1 @@ +['single quote'] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_single_string_no_double_quotes.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_single_string_no_double_quotes.json new file mode 100755 index 000000000..f2ba8f84a --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_single_string_no_double_quotes.json @@ -0,0 +1 @@ +abc \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_start_escape_unclosed.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_start_escape_unclosed.json new file mode 100644 index 000000000..db62a46fc --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_start_escape_unclosed.json @@ -0,0 +1 @@ +["\ \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_unescaped_ctrl_char.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_unescaped_ctrl_char.json new file mode 100755 index 000000000..9f2134807 Binary files /dev/null and b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_unescaped_ctrl_char.json differ diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_unescaped_newline.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_unescaped_newline.json new file mode 100644 index 000000000..700d36086 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_unescaped_newline.json @@ -0,0 +1,2 @@ +["new +line"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_unescaped_tab.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_unescaped_tab.json new file mode 100644 index 000000000..160264a2d --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_unescaped_tab.json @@ -0,0 +1 @@ +[" "] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_unicode_CapitalU.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_unicode_CapitalU.json new file mode 100644 index 000000000..17332bb17 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_unicode_CapitalU.json @@ -0,0 +1 @@ +"\UA66D" \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_string_with_trailing_garbage.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_with_trailing_garbage.json new file mode 100644 index 000000000..efe3bd272 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_string_with_trailing_garbage.json @@ -0,0 +1 @@ +""x \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_100000_opening_arrays.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_100000_opening_arrays.json new file mode 100644 index 000000000..a4823eecc --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_100000_opening_arrays.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_U+2060_word_joined.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_U+2060_word_joined.json new file mode 100644 index 000000000..81156a699 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_U+2060_word_joined.json @@ -0,0 +1 @@ +[⁠] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_UTF8_BOM_no_data.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_UTF8_BOM_no_data.json new file mode 100755 index 000000000..5f282702b --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_UTF8_BOM_no_data.json @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_angle_bracket_..json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_angle_bracket_..json new file mode 100755 index 000000000..a56fef0b0 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_angle_bracket_..json @@ -0,0 +1 @@ +<.> \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_angle_bracket_null.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_angle_bracket_null.json new file mode 100755 index 000000000..617f26254 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_angle_bracket_null.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_array_trailing_garbage.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_array_trailing_garbage.json new file mode 100644 index 000000000..5a745e6f3 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_array_trailing_garbage.json @@ -0,0 +1 @@ +[1]x \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_array_with_extra_array_close.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_array_with_extra_array_close.json new file mode 100755 index 000000000..6cfb1398d --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_array_with_extra_array_close.json @@ -0,0 +1 @@ +[1]] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_array_with_unclosed_string.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_array_with_unclosed_string.json new file mode 100755 index 000000000..ba6b1788b --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_array_with_unclosed_string.json @@ -0,0 +1 @@ +["asd] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_ascii-unicode-identifier.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_ascii-unicode-identifier.json new file mode 100644 index 000000000..ef2ab62fe --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_ascii-unicode-identifier.json @@ -0,0 +1 @@ +aå \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_capitalized_True.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_capitalized_True.json new file mode 100755 index 000000000..7cd88469a --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_capitalized_True.json @@ -0,0 +1 @@ +[True] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_close_unopened_array.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_close_unopened_array.json new file mode 100755 index 000000000..d2af0c646 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_close_unopened_array.json @@ -0,0 +1 @@ +1] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_comma_instead_of_closing_brace.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_comma_instead_of_closing_brace.json new file mode 100644 index 000000000..ac61b8200 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_comma_instead_of_closing_brace.json @@ -0,0 +1 @@ +{"x": true, \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_double_array.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_double_array.json new file mode 100755 index 000000000..058d1626e --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_double_array.json @@ -0,0 +1 @@ +[][] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_end_array.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_end_array.json new file mode 100644 index 000000000..54caf60b1 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_end_array.json @@ -0,0 +1 @@ +] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_incomplete_UTF8_BOM.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_incomplete_UTF8_BOM.json new file mode 100755 index 000000000..bfcdd514f --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_incomplete_UTF8_BOM.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_lone-invalid-utf-8.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_lone-invalid-utf-8.json new file mode 100644 index 000000000..8b1296cad --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_lone-invalid-utf-8.json @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_lone-open-bracket.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_lone-open-bracket.json new file mode 100644 index 000000000..8e2f0bef1 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_lone-open-bracket.json @@ -0,0 +1 @@ +[ \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_no_data.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_no_data.json new file mode 100644 index 000000000..e69de29bb diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_null-byte-outside-string.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_null-byte-outside-string.json new file mode 100644 index 000000000..326db1442 Binary files /dev/null and b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_null-byte-outside-string.json differ diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_number_with_trailing_garbage.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_number_with_trailing_garbage.json new file mode 100644 index 000000000..0746539d2 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_number_with_trailing_garbage.json @@ -0,0 +1 @@ +2@ \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_object_followed_by_closing_object.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_object_followed_by_closing_object.json new file mode 100644 index 000000000..aa9ebaec5 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_object_followed_by_closing_object.json @@ -0,0 +1 @@ +{}} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_object_unclosed_no_value.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_object_unclosed_no_value.json new file mode 100644 index 000000000..17d045147 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_object_unclosed_no_value.json @@ -0,0 +1 @@ +{"": \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_object_with_comment.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_object_with_comment.json new file mode 100644 index 000000000..ed1b569b7 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_object_with_comment.json @@ -0,0 +1 @@ +{"a":/*comment*/"b"} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_object_with_trailing_garbage.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_object_with_trailing_garbage.json new file mode 100644 index 000000000..9ca2336d7 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_object_with_trailing_garbage.json @@ -0,0 +1 @@ +{"a": true} "x" \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_array_apostrophe.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_array_apostrophe.json new file mode 100644 index 000000000..8bebe3af0 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_array_apostrophe.json @@ -0,0 +1 @@ +[' \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_array_comma.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_array_comma.json new file mode 100644 index 000000000..6295fdc36 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_array_comma.json @@ -0,0 +1 @@ +[, \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_array_object.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_array_object.json new file mode 100644 index 000000000..e870445b2 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_array_object.json @@ -0,0 +1 @@ +[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"": diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_array_open_object.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_array_open_object.json new file mode 100644 index 000000000..7a63c8c57 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_array_open_object.json @@ -0,0 +1 @@ +[{ \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_array_open_string.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_array_open_string.json new file mode 100644 index 000000000..9822a6baf --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_array_open_string.json @@ -0,0 +1 @@ +["a \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_array_string.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_array_string.json new file mode 100644 index 000000000..42a619362 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_array_string.json @@ -0,0 +1 @@ +["a" \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_object.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_object.json new file mode 100644 index 000000000..81750b96f --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_object.json @@ -0,0 +1 @@ +{ \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_object_close_array.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_object_close_array.json new file mode 100755 index 000000000..eebc700a1 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_object_close_array.json @@ -0,0 +1 @@ +{] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_object_comma.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_object_comma.json new file mode 100644 index 000000000..47bc9106f --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_object_comma.json @@ -0,0 +1 @@ +{, \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_object_open_array.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_object_open_array.json new file mode 100644 index 000000000..381ede5de --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_object_open_array.json @@ -0,0 +1 @@ +{[ \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_object_open_string.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_object_open_string.json new file mode 100644 index 000000000..328c30cd7 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_object_open_string.json @@ -0,0 +1 @@ +{"a \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_object_string_with_apostrophes.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_object_string_with_apostrophes.json new file mode 100644 index 000000000..9dba17090 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_object_string_with_apostrophes.json @@ -0,0 +1 @@ +{'a' \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_open.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_open.json new file mode 100644 index 000000000..841fd5f86 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_open_open.json @@ -0,0 +1 @@ +["\{["\{["\{["\{ \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_single_eacute.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_single_eacute.json new file mode 100644 index 000000000..92a39f398 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_single_eacute.json @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_single_star.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_single_star.json new file mode 100755 index 000000000..f59ec20aa --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_single_star.json @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_trailing_#.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_trailing_#.json new file mode 100644 index 000000000..898611087 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_trailing_#.json @@ -0,0 +1 @@ +{"a":"b"}#{} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_uescaped_LF_before_string.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_uescaped_LF_before_string.json new file mode 100755 index 000000000..df2f0f242 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_uescaped_LF_before_string.json @@ -0,0 +1 @@ +[\u000A""] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_unclosed_array.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_unclosed_array.json new file mode 100755 index 000000000..11209515c --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_unclosed_array.json @@ -0,0 +1 @@ +[1 \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_unclosed_array_partial_null.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_unclosed_array_partial_null.json new file mode 100644 index 000000000..0d591762c --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_unclosed_array_partial_null.json @@ -0,0 +1 @@ +[ false, nul \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_unclosed_array_unfinished_false.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_unclosed_array_unfinished_false.json new file mode 100644 index 000000000..a2ff8504a --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_unclosed_array_unfinished_false.json @@ -0,0 +1 @@ +[ true, fals \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_unclosed_array_unfinished_true.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_unclosed_array_unfinished_true.json new file mode 100644 index 000000000..3149e8f5a --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_unclosed_array_unfinished_true.json @@ -0,0 +1 @@ +[ false, tru \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_unclosed_object.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_unclosed_object.json new file mode 100755 index 000000000..694d69dbd --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_unclosed_object.json @@ -0,0 +1 @@ +{"asd":"asd" \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_unicode-identifier.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_unicode-identifier.json new file mode 100644 index 000000000..7284aea33 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_unicode-identifier.json @@ -0,0 +1 @@ +å \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_whitespace_U+2060_word_joiner.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_whitespace_U+2060_word_joiner.json new file mode 100755 index 000000000..81156a699 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_whitespace_U+2060_word_joiner.json @@ -0,0 +1 @@ +[⁠] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_whitespace_formfeed.json b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_whitespace_formfeed.json new file mode 100755 index 000000000..a9ea535d1 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/n_structure_whitespace_formfeed.json @@ -0,0 +1 @@ +[ ] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_array_arraysWithSpaces.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_arraysWithSpaces.json new file mode 100755 index 000000000..582290798 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_arraysWithSpaces.json @@ -0,0 +1 @@ +[[] ] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_array_empty-string.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_empty-string.json new file mode 100644 index 000000000..93b6be2bc --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_empty-string.json @@ -0,0 +1 @@ +[""] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_array_empty.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_empty.json new file mode 100755 index 000000000..0637a088a --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_empty.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_array_ending_with_newline.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_ending_with_newline.json new file mode 100755 index 000000000..eac5f7b46 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_ending_with_newline.json @@ -0,0 +1 @@ +["a"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_array_false.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_false.json new file mode 100644 index 000000000..67b2f0760 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_false.json @@ -0,0 +1 @@ +[false] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_array_heterogeneous.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_heterogeneous.json new file mode 100755 index 000000000..d3c1e2648 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_heterogeneous.json @@ -0,0 +1 @@ +[null, 1, "1", {}] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_array_null.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_null.json new file mode 100644 index 000000000..500db4a86 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_null.json @@ -0,0 +1 @@ +[null] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_array_with_1_and_newline.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_with_1_and_newline.json new file mode 100644 index 000000000..994825500 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_with_1_and_newline.json @@ -0,0 +1,2 @@ +[1 +] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_array_with_leading_space.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_with_leading_space.json new file mode 100755 index 000000000..18bfe6422 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_with_leading_space.json @@ -0,0 +1 @@ + [1] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_array_with_several_null.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_with_several_null.json new file mode 100755 index 000000000..99f6c5d1d --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_with_several_null.json @@ -0,0 +1 @@ +[1,null,null,null,2] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_array_with_trailing_space.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_with_trailing_space.json new file mode 100755 index 000000000..de9e7a944 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_array_with_trailing_space.json @@ -0,0 +1 @@ +[2] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_number.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_number.json new file mode 100644 index 000000000..e5f5cc334 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_number.json @@ -0,0 +1 @@ +[123e65] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_number_0e+1.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_0e+1.json new file mode 100755 index 000000000..d1d396706 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_0e+1.json @@ -0,0 +1 @@ +[0e+1] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_number_0e1.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_0e1.json new file mode 100755 index 000000000..3283a7936 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_0e1.json @@ -0,0 +1 @@ +[0e1] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_number_after_space.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_after_space.json new file mode 100644 index 000000000..623570d96 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_after_space.json @@ -0,0 +1 @@ +[ 4] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_number_double_close_to_zero.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_double_close_to_zero.json new file mode 100755 index 000000000..96555ff78 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_double_close_to_zero.json @@ -0,0 +1 @@ +[-0.000000000000000000000000000000000000000000000000000000000000000000000000000001] diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_number_int_with_exp.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_int_with_exp.json new file mode 100755 index 000000000..a4ca9e754 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_int_with_exp.json @@ -0,0 +1 @@ +[20e1] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_number_minus_zero.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_minus_zero.json new file mode 100755 index 000000000..37af1312a --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_minus_zero.json @@ -0,0 +1 @@ +[-0] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_number_negative_int.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_negative_int.json new file mode 100644 index 000000000..8e30f8bd9 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_negative_int.json @@ -0,0 +1 @@ +[-123] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_number_negative_one.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_negative_one.json new file mode 100644 index 000000000..99d21a2a0 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_negative_one.json @@ -0,0 +1 @@ +[-1] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_number_negative_zero.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_negative_zero.json new file mode 100644 index 000000000..37af1312a --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_negative_zero.json @@ -0,0 +1 @@ +[-0] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_capital_e.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_capital_e.json new file mode 100644 index 000000000..6edbdfcb1 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_capital_e.json @@ -0,0 +1 @@ +[1E22] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_capital_e_neg_exp.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_capital_e_neg_exp.json new file mode 100644 index 000000000..0a01bd3ef --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_capital_e_neg_exp.json @@ -0,0 +1 @@ +[1E-2] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_capital_e_pos_exp.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_capital_e_pos_exp.json new file mode 100644 index 000000000..5a8fc0972 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_capital_e_pos_exp.json @@ -0,0 +1 @@ +[1E+2] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_exponent.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_exponent.json new file mode 100644 index 000000000..da2522d61 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_exponent.json @@ -0,0 +1 @@ +[123e45] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_fraction_exponent.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_fraction_exponent.json new file mode 100644 index 000000000..3944a7a45 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_fraction_exponent.json @@ -0,0 +1 @@ +[123.456e78] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_neg_exp.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_neg_exp.json new file mode 100644 index 000000000..ca40d3c25 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_neg_exp.json @@ -0,0 +1 @@ +[1e-2] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_pos_exponent.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_pos_exponent.json new file mode 100644 index 000000000..343601d51 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_real_pos_exponent.json @@ -0,0 +1 @@ +[1e+2] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_number_simple_int.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_simple_int.json new file mode 100644 index 000000000..e47f69afc --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_simple_int.json @@ -0,0 +1 @@ +[123] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_number_simple_real.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_simple_real.json new file mode 100644 index 000000000..b02878e5f --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_number_simple_real.json @@ -0,0 +1 @@ +[123.456789] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_object.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_object.json new file mode 100755 index 000000000..78262eda3 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_object.json @@ -0,0 +1 @@ +{"asd":"sdf", "dfg":"fgh"} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_object_basic.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_basic.json new file mode 100755 index 000000000..646bbe7bb --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_basic.json @@ -0,0 +1 @@ +{"asd":"sdf"} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_object_duplicated_key.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_duplicated_key.json new file mode 100755 index 000000000..bbc2e1ce4 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_duplicated_key.json @@ -0,0 +1 @@ +{"a":"b","a":"c"} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_object_duplicated_key_and_value.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_duplicated_key_and_value.json new file mode 100755 index 000000000..211581c20 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_duplicated_key_and_value.json @@ -0,0 +1 @@ +{"a":"b","a":"b"} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_object_empty.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_empty.json new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_empty.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_object_empty_key.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_empty_key.json new file mode 100755 index 000000000..c0013d3b8 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_empty_key.json @@ -0,0 +1 @@ +{"":0} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_object_escaped_null_in_key.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_escaped_null_in_key.json new file mode 100644 index 000000000..593f0f67f --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_escaped_null_in_key.json @@ -0,0 +1 @@ +{"foo\u0000bar": 42} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_object_extreme_numbers.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_extreme_numbers.json new file mode 100644 index 000000000..a0d3531c3 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_extreme_numbers.json @@ -0,0 +1 @@ +{ "min": -1.0e+28, "max": 1.0e+28 } \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_object_long_strings.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_long_strings.json new file mode 100644 index 000000000..bdc4a0871 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_long_strings.json @@ -0,0 +1 @@ +{"x":[{"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}], "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_object_simple.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_simple.json new file mode 100644 index 000000000..dacac917f --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_simple.json @@ -0,0 +1 @@ +{"a":[]} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_object_string_unicode.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_string_unicode.json new file mode 100644 index 000000000..8effdb297 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_string_unicode.json @@ -0,0 +1 @@ +{"title":"\u041f\u043e\u043b\u0442\u043e\u0440\u0430 \u0417\u0435\u043c\u043b\u0435\u043a\u043e\u043f\u0430" } \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_object_with_newlines.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_with_newlines.json new file mode 100644 index 000000000..246ec6b34 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_object_with_newlines.json @@ -0,0 +1,3 @@ +{ +"a": "b" +} \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json new file mode 100755 index 000000000..9967ddeb8 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json @@ -0,0 +1 @@ +["\u0060\u012a\u12AB"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_accepted_surrogate_pair.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_accepted_surrogate_pair.json new file mode 100755 index 000000000..996875cc8 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_accepted_surrogate_pair.json @@ -0,0 +1 @@ +["\uD801\udc37"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_accepted_surrogate_pairs.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_accepted_surrogate_pairs.json new file mode 100755 index 000000000..3401021ec --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_accepted_surrogate_pairs.json @@ -0,0 +1 @@ +["\ud83d\ude39\ud83d\udc8d"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_allowed_escapes.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_allowed_escapes.json new file mode 100644 index 000000000..7f495532f --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_allowed_escapes.json @@ -0,0 +1 @@ +["\"\\\/\b\f\n\r\t"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_backslash_and_u_escaped_zero.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_backslash_and_u_escaped_zero.json new file mode 100755 index 000000000..d4439eda7 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_backslash_and_u_escaped_zero.json @@ -0,0 +1 @@ +["\\u0000"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_backslash_doublequotes.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_backslash_doublequotes.json new file mode 100644 index 000000000..ae03243b6 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_backslash_doublequotes.json @@ -0,0 +1 @@ +["\""] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_comments.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_comments.json new file mode 100644 index 000000000..2260c20c2 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_comments.json @@ -0,0 +1 @@ +["a/*b*/c/*d//e"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_double_escape_a.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_double_escape_a.json new file mode 100644 index 000000000..6715d6f40 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_double_escape_a.json @@ -0,0 +1 @@ +["\\a"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_double_escape_n.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_double_escape_n.json new file mode 100644 index 000000000..44ca56c4d --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_double_escape_n.json @@ -0,0 +1 @@ +["\\n"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_escaped_control_character.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_escaped_control_character.json new file mode 100644 index 000000000..5b014a9c2 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_escaped_control_character.json @@ -0,0 +1 @@ +["\u0012"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_escaped_noncharacter.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_escaped_noncharacter.json new file mode 100755 index 000000000..2ff52e2c5 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_escaped_noncharacter.json @@ -0,0 +1 @@ +["\uFFFF"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_in_array.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_in_array.json new file mode 100755 index 000000000..21d7ae4cd --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_in_array.json @@ -0,0 +1 @@ +["asd"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_in_array_with_leading_space.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_in_array_with_leading_space.json new file mode 100755 index 000000000..9e1887c1e --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_in_array_with_leading_space.json @@ -0,0 +1 @@ +[ "asd"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_last_surrogates_1_and_2.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_last_surrogates_1_and_2.json new file mode 100644 index 000000000..3919cef76 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_last_surrogates_1_and_2.json @@ -0,0 +1 @@ +["\uDBFF\uDFFF"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_nbsp_uescaped.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_nbsp_uescaped.json new file mode 100644 index 000000000..2085ab1a1 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_nbsp_uescaped.json @@ -0,0 +1 @@ +["new\u00A0line"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json new file mode 100755 index 000000000..059e4d9dd --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json @@ -0,0 +1 @@ +["􏿿"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json new file mode 100755 index 000000000..4c913bd41 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json @@ -0,0 +1 @@ +["￿"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_null_escape.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_null_escape.json new file mode 100644 index 000000000..c1ad84404 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_null_escape.json @@ -0,0 +1 @@ +["\u0000"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_one-byte-utf-8.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_one-byte-utf-8.json new file mode 100644 index 000000000..157185923 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_one-byte-utf-8.json @@ -0,0 +1 @@ +["\u002c"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_pi.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_pi.json new file mode 100644 index 000000000..9df11ae88 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_pi.json @@ -0,0 +1 @@ +["π"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_reservedCharacterInUTF-8_U+1BFFF.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_reservedCharacterInUTF-8_U+1BFFF.json new file mode 100755 index 000000000..10a33a171 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_reservedCharacterInUTF-8_U+1BFFF.json @@ -0,0 +1 @@ +["𛿿"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_simple_ascii.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_simple_ascii.json new file mode 100644 index 000000000..8cadf7d05 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_simple_ascii.json @@ -0,0 +1 @@ +["asd "] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_space.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_space.json new file mode 100644 index 000000000..efd782cc3 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_space.json @@ -0,0 +1 @@ +" " \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json new file mode 100755 index 000000000..7620b6655 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json @@ -0,0 +1 @@ +["\uD834\uDd1e"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_three-byte-utf-8.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_three-byte-utf-8.json new file mode 100644 index 000000000..108f1d67d --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_three-byte-utf-8.json @@ -0,0 +1 @@ +["\u0821"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_two-byte-utf-8.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_two-byte-utf-8.json new file mode 100644 index 000000000..461503c31 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_two-byte-utf-8.json @@ -0,0 +1 @@ +["\u0123"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_u+2028_line_sep.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_u+2028_line_sep.json new file mode 100755 index 000000000..897b6021a --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_u+2028_line_sep.json @@ -0,0 +1 @@ +["
"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_u+2029_par_sep.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_u+2029_par_sep.json new file mode 100755 index 000000000..8cd998c89 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_u+2029_par_sep.json @@ -0,0 +1 @@ +["
"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_uEscape.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_uEscape.json new file mode 100755 index 000000000..f7b41a02f --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_uEscape.json @@ -0,0 +1 @@ +["\u0061\u30af\u30EA\u30b9"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_uescaped_newline.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_uescaped_newline.json new file mode 100644 index 000000000..3a5a220b6 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_uescaped_newline.json @@ -0,0 +1 @@ +["new\u000Aline"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unescaped_char_delete.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unescaped_char_delete.json new file mode 100755 index 000000000..7d064f498 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unescaped_char_delete.json @@ -0,0 +1 @@ +[""] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode.json new file mode 100644 index 000000000..3598095b7 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode.json @@ -0,0 +1 @@ +["\uA66D"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicodeEscapedBackslash.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicodeEscapedBackslash.json new file mode 100755 index 000000000..0bb3b51e7 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicodeEscapedBackslash.json @@ -0,0 +1 @@ +["\u005C"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_2.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_2.json new file mode 100644 index 000000000..a7dcb9768 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_2.json @@ -0,0 +1 @@ +["⍂㈴⍂"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_U+10FFFE_nonchar.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_U+10FFFE_nonchar.json new file mode 100644 index 000000000..9a8370b96 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_U+10FFFE_nonchar.json @@ -0,0 +1 @@ +["\uDBFF\uDFFE"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_U+1FFFE_nonchar.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_U+1FFFE_nonchar.json new file mode 100644 index 000000000..c51f8ae45 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_U+1FFFE_nonchar.json @@ -0,0 +1 @@ +["\uD83F\uDFFE"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json new file mode 100644 index 000000000..626d5f815 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json @@ -0,0 +1 @@ +["\u200B"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_U+2064_invisible_plus.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_U+2064_invisible_plus.json new file mode 100644 index 000000000..1e23972c6 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_U+2064_invisible_plus.json @@ -0,0 +1 @@ +["\u2064"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_U+FDD0_nonchar.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_U+FDD0_nonchar.json new file mode 100644 index 000000000..18ef151b4 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_U+FDD0_nonchar.json @@ -0,0 +1 @@ +["\uFDD0"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_U+FFFE_nonchar.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_U+FFFE_nonchar.json new file mode 100644 index 000000000..13d261fda --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_U+FFFE_nonchar.json @@ -0,0 +1 @@ +["\uFFFE"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_escaped_double_quote.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_escaped_double_quote.json new file mode 100755 index 000000000..4e6257856 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_unicode_escaped_double_quote.json @@ -0,0 +1 @@ +["\u0022"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_utf8.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_utf8.json new file mode 100644 index 000000000..40878435f --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_utf8.json @@ -0,0 +1 @@ +["€𝄞"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_string_with_del_character.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_with_del_character.json new file mode 100755 index 000000000..8bd24907d --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_string_with_del_character.json @@ -0,0 +1 @@ +["aa"] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_lonely_false.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_lonely_false.json new file mode 100644 index 000000000..02e4a84d6 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_lonely_false.json @@ -0,0 +1 @@ +false \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_lonely_int.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_lonely_int.json new file mode 100755 index 000000000..f70d7bba4 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_lonely_int.json @@ -0,0 +1 @@ +42 \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_lonely_negative_real.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_lonely_negative_real.json new file mode 100755 index 000000000..b5135a207 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_lonely_negative_real.json @@ -0,0 +1 @@ +-0.1 \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_lonely_null.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_lonely_null.json new file mode 100644 index 000000000..ec747fa47 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_lonely_null.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_lonely_string.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_lonely_string.json new file mode 100755 index 000000000..b6e982ca9 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_lonely_string.json @@ -0,0 +1 @@ +"asd" \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_lonely_true.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_lonely_true.json new file mode 100755 index 000000000..f32a5804e --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_lonely_true.json @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_string_empty.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_string_empty.json new file mode 100644 index 000000000..3cc762b55 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_string_empty.json @@ -0,0 +1 @@ +"" \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_trailing_newline.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_trailing_newline.json new file mode 100644 index 000000000..0c3426d4c --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_trailing_newline.json @@ -0,0 +1 @@ +["a"] diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_true_in_array.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_true_in_array.json new file mode 100644 index 000000000..de601e305 --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_true_in_array.json @@ -0,0 +1 @@ +[true] \ No newline at end of file diff --git a/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_whitespace_array.json b/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_whitespace_array.json new file mode 100644 index 000000000..2bedf7f2d --- /dev/null +++ b/transport/http/protocol/internal/json/testdata/test_parsing/y_structure_whitespace_array.json @@ -0,0 +1 @@ + [] \ No newline at end of file diff --git a/transport/http/protocol/internal/query/shape_deserializer.go b/transport/http/protocol/internal/query/shape_deserializer.go new file mode 100644 index 000000000..179ab3d4c --- /dev/null +++ b/transport/http/protocol/internal/query/shape_deserializer.go @@ -0,0 +1,4 @@ +package query + +// ShapeDeserializer doesn't exist for query. The awsquery and ec2query +// protocols use XML responses. diff --git a/transport/http/protocol/internal/query/shape_serializer.go b/transport/http/protocol/internal/query/shape_serializer.go new file mode 100644 index 000000000..b8d679824 --- /dev/null +++ b/transport/http/protocol/internal/query/shape_serializer.go @@ -0,0 +1,500 @@ +package query + +import ( + "encoding/base64" + "math" + "math/big" + "net/url" + "sort" + "strconv" + "strings" + "time" + + "github.com/aws/smithy-go" + "github.com/aws/smithy-go/document" + "github.com/aws/smithy-go/internal/serde" + smithytime "github.com/aws/smithy-go/time" + "github.com/aws/smithy-go/traits" +) + +type ctxKind byte + +const ( + ctxKindList ctxKind = iota + ctxKindMap + ctxKindMapValue + ctxKindStruct + + ctxKindNone ctxKind = 0xff +) + +type serCtx struct { + kind ctxKind + flattened bool + prefix string // popped back onto s.currPrefix as you pop out the stack + + listIndex int + listPrefix string + + mapIndex int + mapKeyName string + mapValueName string + mapBuf []mapBufEntry +} + +// ShapeSerializer serializes Smithy shapes to the AWS query string format. +type ShapeSerializer struct { + opts ShapeSerializerOptions + + values url.Values + stack serde.Stack[serCtx] + currPrefix string // runs as values are written e.g. for list +} + +type mapBufEntry struct { + prefix string + key string + values []kv +} + +type kv struct { + k, v string +} + +// ShapeSerializerOptions configures a ShapeSerializer. +type ShapeSerializerOptions struct { + // Adjusts serialization for the ec2Query protocol: + // - Member names resolve via @ec2QueryName, then @xmlName + // capitalized, then member name capitalized (instead of @xmlName + // or member name as-is). + // - All lists serialize as flat regardless of @xmlFlattened. + // - Empty lists are omitted (instead of emitting a "Key=" sentinel). + EC2Mode bool +} + +var _ smithy.ShapeSerializer = (*ShapeSerializer)(nil) + +// NewShapeSerializer returns a new ShapeSerializer. +func NewShapeSerializer(action, version string, opts ...func(*ShapeSerializerOptions)) *ShapeSerializer { + o := ShapeSerializerOptions{} + for _, fn := range opts { + fn(&o) + } + v := url.Values{} + v.Set("Action", action) + v.Set("Version", version) + return &ShapeSerializer{values: v, stack: serde.NewStack[serCtx](), opts: o} +} + +// Bytes returns the encoded query string as bytes. +func (s *ShapeSerializer) Bytes() []byte { + keys := make([]string, 0, len(s.values)) + for k := range s.values { + keys = append(keys, k) + } + sort.Strings(keys) + + var buf strings.Builder + for _, k := range keys { + for _, v := range s.values[k] { + if buf.Len() > 0 { + buf.WriteByte('&') + } + buf.WriteString(url.QueryEscape(k)) + buf.WriteByte('=') + buf.WriteString(url.QueryEscape(v)) + } + } + return []byte(buf.String()) +} + +func (s *ShapeSerializer) top() ctxKind { + t := s.stack.Top() + if t == nil { + return ctxKindNone + } + return t.kind +} + +func (s *ShapeSerializer) memberName(schema *smithy.Schema) string { + if s.opts.EC2Mode { + return ec2MemberName(schema) + } + if xn, ok := smithy.SchemaTrait[*traits.XMLName](schema); ok { + return xn.Name + } + return schema.MemberName() +} + +func (s *ShapeSerializer) appendMemberPrefix(schema *smithy.Schema) { + name := s.memberName(schema) + if name == "" { + return + } + if s.currPrefix != "" { + s.currPrefix = s.currPrefix + "." + name + } else { + s.currPrefix = name + } +} + +// nexus point (alongside writeValue) through which basically every write flows +// to handle putting the appropriate prefix in place +func (s *ShapeSerializer) resolveKey(schema *smithy.Schema) string { + switch s.top() { + case ctxKindMapValue: + valPrefix := s.consumeMapValue() + return valPrefix + case ctxKindList: + ctx := s.stack.Top() + ctx.listIndex++ + return s.currPrefix + "." + strconv.Itoa(ctx.listIndex) + default: + name := s.memberName(schema) + if name == "" { + return s.currPrefix + } + if s.currPrefix == "" { + return name + } + return s.currPrefix + "." + name + } +} + +func (s *ShapeSerializer) writeValue(key, value string) { + if s.bufferMapEntry(key, value) { + return + } + + s.values.Add(key, value) +} + +func (s *ShapeSerializer) bufferMapEntry(key, value string) bool { + vals := s.stack.Values() + for i := len(vals) - 1; i >= 0; i-- { + ctx := &vals[i] + if ctx.kind != ctxKindMap { + continue + } + if len(ctx.mapBuf) == 0 { + return false + } + + entry := &ctx.mapBuf[len(ctx.mapBuf)-1] + suffix := strings.TrimPrefix(key, entry.prefix) + entry.values = append(entry.values, kv{k: suffix, v: value}) + return true + } + + return false +} + +// WriteBool implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteBool(schema *smithy.Schema, v bool) { + if v { + s.writeValue(s.resolveKey(schema), "true") + } else { + s.writeValue(s.resolveKey(schema), "false") + } +} + +// WriteInt8 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt8(schema *smithy.Schema, v int8) { writeInt(s, schema, v) } + +// WriteInt16 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt16(schema *smithy.Schema, v int16) { writeInt(s, schema, v) } + +// WriteInt32 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt32(schema *smithy.Schema, v int32) { writeInt(s, schema, v) } + +// WriteInt64 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt64(schema *smithy.Schema, v int64) { writeInt(s, schema, v) } + +// WriteFloat32 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteFloat32(schema *smithy.Schema, v float32) { writeFloat(s, schema, v) } + +// WriteFloat64 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteFloat64(schema *smithy.Schema, v float64) { writeFloat(s, schema, v) } + +func writeInt[T int8 | int16 | int32 | int64](s *ShapeSerializer, schema *smithy.Schema, v T) { + s.writeValue(s.resolveKey(schema), strconv.FormatInt(int64(v), 10)) +} + +func writeFloat[T float32 | float64](s *ShapeSerializer, schema *smithy.Schema, v T) { + s.writeValue(s.resolveKey(schema), formatFloat(float64(v))) +} + +func formatFloat(v float64) string { + switch { + case math.IsInf(v, 1): + return "Infinity" + case math.IsInf(v, -1): + return "-Infinity" + case math.IsNaN(v): + return "NaN" + default: + return strconv.FormatFloat(v, 'f', -1, 64) + } +} + +// WriteString implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteString(schema *smithy.Schema, v string) { + s.writeValue(s.resolveKey(schema), v) +} + +// WriteBlob implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteBlob(schema *smithy.Schema, v []byte) { + if v == nil { + return + } + + s.writeValue(s.resolveKey(schema), base64.StdEncoding.EncodeToString(v)) +} + +// WriteTime implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteTime(schema *smithy.Schema, v time.Time) { + format := "date-time" + if t, ok := smithy.SchemaTrait[*traits.TimestampFormat](schema); ok { + format = t.Format + } + + var sv string + switch format { + case "date-time": + sv = smithytime.FormatDateTime(v) + case "http-date": + sv = smithytime.FormatHTTPDate(v) + case "epoch-seconds": + sv = strconv.FormatFloat(smithytime.FormatEpochSeconds(v), 'f', -1, 64) + default: + sv = smithytime.FormatDateTime(v) + } + + s.writeValue(s.resolveKey(schema), sv) +} + +// WriteStruct implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteStruct(schema *smithy.Schema) { + saved := s.currPrefix + switch s.top() { + case ctxKindMapValue: + valPrefix := s.consumeMapValue() + s.currPrefix, saved = valPrefix, s.currPrefix + case ctxKindList: + ctx := s.stack.Top() + ctx.listIndex++ + s.currPrefix = s.currPrefix + "." + strconv.Itoa(ctx.listIndex) + default: + s.appendMemberPrefix(schema) + } + + s.stack.Push(serCtx{kind: ctxKindStruct, prefix: saved}) +} + +// CloseStruct implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseStruct() { + ctx := s.stack.Pop() + s.currPrefix = ctx.prefix +} + +// WriteUnion implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteUnion(schema, variant *smithy.Schema) { + if s.top() == ctxKindMapValue { + s.stack.Pop() + } + s.stack.Push(serCtx{kind: ctxKindNone, prefix: s.currPrefix}) + s.appendMemberPrefix(schema) +} + +// CloseUnion implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseUnion() { + ctx := s.stack.Pop() + s.currPrefix = ctx.prefix +} + +// WriteNil implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteNil(_ *smithy.Schema) { + if s.top() == ctxKindMapValue { + s.consumeMapValue() + } +} + +// enterContainer handles the prefix setup common to WriteList and WriteMap. +// It returns the prefix to save for restoration on close. +func (s *ShapeSerializer) enterContainer(schema *smithy.Schema) string { + saved := s.currPrefix + switch s.top() { + case ctxKindMapValue: + // WriteKey set s.prefix to the value path and pushed ctxMapValue + // with the map prefix. Pop ctxMapValue and use its saved prefix + // as the restore point — s.prefix (the value path) is already + // the correct working prefix. + ctx := s.stack.Pop() + saved = ctx.prefix + return saved + case ctxKindList: + ctx := s.stack.Top() + ctx.listIndex++ + s.currPrefix = s.currPrefix + "." + strconv.Itoa(ctx.listIndex) + } + + s.appendMemberPrefix(schema) + return saved +} + +// WriteList implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteList(schema *smithy.Schema) { + saved := s.enterContainer(schema) + + _, flattened := smithy.SchemaTrait[*traits.XMLFlattened](schema) + flattened = flattened || s.opts.EC2Mode + + listPrefix := s.currPrefix + if !flattened { + locName := "member" + if schema != nil { + if lm := schema.ListMember(); lm != nil { + if xn, ok := smithy.SchemaTrait[*traits.XMLName](lm); ok { + locName = xn.Name + } + } + } + s.currPrefix = s.currPrefix + "." + locName + } + + s.stack.Push(serCtx{ + kind: ctxKindList, + flattened: flattened, + prefix: saved, + listPrefix: listPrefix, + }) +} + +// CloseList implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseList() { + if s.top() != ctxKindList { + return + } + + ctx := s.stack.Pop() + if ctx.listIndex == 0 && !s.opts.EC2Mode { + s.writeValue(ctx.listPrefix, "") + } + + s.currPrefix = ctx.prefix +} + +// WriteMap implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteMap(schema *smithy.Schema) { + saved := s.enterContainer(schema) + + _, flattened := smithy.SchemaTrait[*traits.XMLFlattened](schema) + if !flattened { + s.currPrefix = s.currPrefix + ".entry" + } + + keyLoc, valLoc := "key", "value" + if schema != nil { + if mk := schema.MapKey(); mk != nil { + if xn, ok := smithy.SchemaTrait[*traits.XMLName](mk); ok { + keyLoc = xn.Name + } + } + if mv := schema.MapValue(); mv != nil { + if xn, ok := smithy.SchemaTrait[*traits.XMLName](mv); ok { + valLoc = xn.Name + } + } + } + + s.stack.Push(serCtx{ + kind: ctxKindMap, + flattened: flattened, + prefix: saved, + mapKeyName: keyLoc, + mapValueName: valLoc, + }) +} + +// WriteKey implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteKey(_ *smithy.Schema, key string) { + ctx := s.stack.Top() + ctx.mapIndex++ + + prefix := s.currPrefix + "." + strconv.Itoa(ctx.mapIndex) + ctx.mapBuf = append(ctx.mapBuf, mapBufEntry{ + prefix: prefix, + key: key, + }) + + s.writeValue(prefix+"."+ctx.mapKeyName, key) + + // Push ctxMapValue with the current prefix so the next value write can + // restore it. Set s.prefix to the value path. + s.stack.Push(serCtx{kind: ctxKindMapValue, prefix: s.currPrefix}) + s.currPrefix = prefix + "." + ctx.mapValueName +} + +// CloseMap implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseMap() { + if s.top() != ctxKindMap { + return + } + + ctx := s.stack.Pop() + + // Sort entries by map key for deterministic output. + sort.Slice(ctx.mapBuf, func(i, j int) bool { + return ctx.mapBuf[i].key < ctx.mapBuf[j].key + }) + + // Flush with sequential indices. + for i, entry := range ctx.mapBuf { + newPrefix := s.currPrefix + "." + strconv.Itoa(i+1) + for _, kv := range entry.values { + s.writeValue(newPrefix+kv.k, kv.v) + } + } + + s.currPrefix = ctx.prefix +} + +// WriteBigInt is unimplemented. +func (s *ShapeSerializer) WriteBigInt(_ *smithy.Schema, _ *big.Int) { + panic("query: BigInteger not supported") +} + +// WriteBigFloat is unimplemented. +func (s *ShapeSerializer) WriteBigFloat(_ *smithy.Schema, _ *big.Float) { + panic("query: BigDecimal not supported") +} + +// WriteDocument is unimplemented. +func (s *ShapeSerializer) WriteDocument(_ *smithy.Schema, _ document.Value) { + panic("query: Document not supported") +} + +func (s *ShapeSerializer) consumeMapValue() string { + ctx := s.stack.Pop() + valPrefix := s.currPrefix + s.currPrefix = ctx.prefix + return valPrefix +} + +func ec2MemberName(schema *smithy.Schema) string { + if t, ok := smithy.SchemaTrait[*traits.EC2QueryName](schema); ok { + return t.Name + } + if xn, ok := smithy.SchemaTrait[*traits.XMLName](schema); ok { + return capitalize(xn.Name) + } + return capitalize(schema.MemberName()) +} + +func capitalize(s string) string { + if s == "" || s[0] >= 'A' && s[0] <= 'Z' { + return s + } + + return string(s[0]-'a'+'A') + s[1:] +} diff --git a/transport/http/protocol/internal/xml/element_name.go b/transport/http/protocol/internal/xml/element_name.go new file mode 100644 index 000000000..61a778f30 --- /dev/null +++ b/transport/http/protocol/internal/xml/element_name.go @@ -0,0 +1,77 @@ +package xml + +import ( + "github.com/aws/smithy-go" + "github.com/aws/smithy-go/traits" +) + +// Unlike json which has a sane recursive data model, the xml serializer at its +// core operates by writing ... except what ename is supposed to +// be depends on a handful of different factors based on serialization context +// and traits. The helpers here attempt to solve that in the most +// straightforward way possible. + +// base resolver for element names +func (s *ShapeSerializer) ename(schema *smithy.Schema) string { + // DIRECT only, if a member does not have an @xmlName but its target does, + // we don't use the one on the target + if t, ok := smithy.SchemaDirectTrait[*traits.XMLName](schema); ok { + return t.Name + } else if s.stack.Len() == 0 { // the root + return schema.ID().Name + } + + return schema.MemberName() +} + +// wraps ename to check for the context of "am i in a list or map" +func (s *ShapeSerializer) ctxEname(schema *smithy.Schema) string { + if top := s.stack.Top(); top != nil { + switch top.kind { + case ctxKindList: + return top.itemName + case ctxKindMap: + if top.inMapEntry { + return s.ename(top.schema.MapValue()) + } + } + } + + return s.ename(schema) +} + +// structs with @httpPayload are special, preferring in order: +// - member/target @xmlName +// - target shape name +// +// otherwise just use context-based name +func (s *ShapeSerializer) structEname(schema *smithy.Schema) string { + if isPayload(schema) { + if t, ok := smithy.SchemaTrait[*traits.XMLName](schema); ok { + return t.Name + } + return schema.TargetID().Name + } + + return s.ctxEname(schema) +} + +// resolution for @xmlNamespace, which is generally sane +func (s *ShapeSerializer) xmlns(schema *smithy.Schema) *traits.XMLNamespace { + if top := s.stack.Top(); top != nil { + switch { + case top.flat && (top.kind == ctxKindList || top.kind == ctxKindMap): + // flattened lists/maps have no wrapper, inherit the + // collection's namespace if the member doesn't have its own + if _, ok := smithy.SchemaDirectTrait[*traits.XMLNamespace](schema); !ok { + ns, _ := smithy.SchemaDirectTrait[*traits.XMLNamespace](top.schema) + return ns + } + case top.kind == ctxKindMap && !top.inMapEntry: + return nil + } + } + + ns, _ := smithy.SchemaDirectTrait[*traits.XMLNamespace](schema) + return ns +} diff --git a/transport/http/protocol/internal/xml/error.go b/transport/http/protocol/internal/xml/error.go new file mode 100644 index 000000000..366cf491d --- /dev/null +++ b/transport/http/protocol/internal/xml/error.go @@ -0,0 +1,38 @@ +package xml + +import "encoding/xml" + +type protocolError struct { + Code string `xml:"Code"` + Message string `xml:"Message"` +} + +// GetProtocolErrorInfo extracts Smithy error details from a query-protocol +// response. +// +// This version of GetProtocolErrorInfo also handles the extraction of the +// modeled error body from the protocol wrapper so the caller can operate on it +// directly. +func GetProtocolErrorInfo(payload []byte) (code, message string, errorBody []byte, err error) { + code = "UnknownError" + message = code + + errorBody, err = ExtractElement(payload, "Error") + if err != nil || len(errorBody) == 0 { + return + } + + var errinf protocolError + if err = xml.Unmarshal(errorBody, &errinf); err != nil { + return + } + + err = nil + if len(errinf.Code) > 0 { + code = errinf.Code + } + if len(errinf.Message) > 0 { + message = errinf.Message + } + return +} diff --git a/transport/http/protocol/internal/xml/extract.go b/transport/http/protocol/internal/xml/extract.go new file mode 100644 index 000000000..6d6bed8cd --- /dev/null +++ b/transport/http/protocol/internal/xml/extract.go @@ -0,0 +1,77 @@ +package xml + +import ( + "bytes" + "encoding/xml" + "strings" +) + +// ExtractElement finds the first occurrence of the named element in the XML +// and returns it as raw bytes, including the element's opening and closing +// tags. +// +// This is used to extract both success and error responses from the protocol +// XML that wraps them. +// +// For example for awsquery, with an operation output shape named +// XmlListsResult: +// +// +// +// foo +// ... +// +// +// +// ExtractElement(payload, "XmlListsResult") yields: +// +// foo... +// +// ExtractElement will forward the io.EOF returned by the underlying decoder +// if the target element is not found, which the caller can index on if +// they're looking for an optional element. +func ExtractElement(payload []byte, name string) ([]byte, error) { + dec := xml.NewDecoder(bytes.NewReader(payload)) + for { + tok, err := dec.Token() + if err != nil { + return nil, err + } + + start, ok := tok.(xml.StartElement) + if !ok || !strings.EqualFold(start.Name.Local, name) { + continue + } + + var buf bytes.Buffer + enc := xml.NewEncoder(&buf) + if err := enc.EncodeToken(start); err != nil { + return nil, err + } + if err := enc.Flush(); err != nil { + return nil, err + } + + depth := 1 + for depth > 0 { + tok, err := dec.Token() + if err != nil { + return nil, err + } + if err := enc.EncodeToken(tok); err != nil { + return nil, err + } + switch tok.(type) { + case xml.StartElement: + depth++ + case xml.EndElement: + depth-- + } + } + + if err := enc.Flush(); err != nil { + return nil, err + } + return buf.Bytes(), nil + } +} diff --git a/transport/http/protocol/internal/xml/shape_deserializer.go b/transport/http/protocol/internal/xml/shape_deserializer.go new file mode 100644 index 000000000..4717a8b18 --- /dev/null +++ b/transport/http/protocol/internal/xml/shape_deserializer.go @@ -0,0 +1,731 @@ +package xml + +import ( + "bytes" + "encoding/base64" + "encoding/xml" + "fmt" + "math/big" + "math" + "strconv" + "strings" + "time" + + "github.com/aws/smithy-go" + "github.com/aws/smithy-go/document" + "github.com/aws/smithy-go/internal/serde" + smithytime "github.com/aws/smithy-go/time" + "github.com/aws/smithy-go/traits" +) + +type ctxKind byte + +const ( + ctxKindStruct ctxKind = iota + ctxKindList + ctxKindMap +) + +type deserCtx struct { + kind ctxKind + schema *smithy.Schema + flattened bool + + startElem xml.StartElement // cached to read @xmlAttributes + attrIndex int + + // for flattened list/map, ReadStructMember consumes the start element + // when it discovers the member, subsequent calls to ReadMapKey or + // ReadListItem will do that instead + first bool + + // for map, ReadX after ReadMapKey will leave the stream at + // which the next call to ReadMapKey must consume + inMapEntry bool +} + +// ShapeDeserializer implements unmarshaling of XML into Smithy shapes. +// +// ShapeDeserializer consumes whole XML — the payload handed to +// NewShapeDeserializer must include the outermost tag that opens the shape +// being read. This is because the deserializer retains the outer +// StartElement so @xmlAttribute members can draw from its attributes. +// +// For example, an awsquery response body looks like this: +// +// <[OperationName]Response> +// <[OperationName]Result> +// ... +// ... +// ... +// ... +// +// +// ... +// +// +// +// The deserializer must receive the Result element including its opening +// and closing tags — that is: +// +// <[OperationName]Result> +// ... +// ... +// ... +// +// +// The outer Response wrapper must not be included. +type ShapeDeserializer struct { + dec *xml.Decoder + peeked xml.Token + stack serde.Stack[deserCtx] + + // most recent start element we saw in the token stream, when we go into a + // struct context we grab it so we can read any @xmlAttributes + currStart *xml.StartElement + currAttr *string +} + +var _ smithy.ShapeDeserializer = (*ShapeDeserializer)(nil) + +// NewShapeDeserializer returns a new ShapeDeserializer. +func NewShapeDeserializer(p []byte) *ShapeDeserializer { + return &ShapeDeserializer{ + dec: xml.NewDecoder(bytes.NewReader(p)), + stack: serde.NewStack[deserCtx](), + } +} + +func xmlMemberName(schema *smithy.Schema) string { + if t, ok := smithy.SchemaDirectTrait[*traits.XMLName](schema); ok { + return t.Name + } + return schema.MemberName() +} + +func findMember(schema *smithy.Schema, elemName string) *smithy.Schema { + if schema == nil { + return nil + } + + for _, m := range schema.Members() { + mName := xmlMemberName(m) + if strings.EqualFold(mName, elemName) { + return m + } + } + return nil +} + +// ReadStruct implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadStruct(s *smithy.Schema) error { + if err := d.ensureStart(); err != nil { + return err + } + + start := *d.currStart + d.stack.Push(deserCtx{kind: ctxKindStruct, schema: s, startElem: start}) + return nil +} + +// ReadStructMember implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadStructMember() (*smithy.Schema, error) { + ctx := d.stack.Top() + if ctx == nil || ctx.kind != ctxKindStruct { + return nil, fmt.Errorf("ReadStructMember called without ReadStruct") + } + + if m := d.nextAttrMember(ctx); m != nil { + return m, nil + } + + for { + start, ok, err := d.nextStart() + if err != nil { + return nil, err + } + if !ok { + d.stack.Pop() + return nil, nil + } + + member := findMember(ctx.schema, start.Name.Local) + if member == nil { + if err := d.skip(); err != nil { + return nil, err + } + continue + } + + if _, isAttr := smithy.SchemaTrait[*traits.XMLAttribute](member); isAttr { + if err := d.skip(); err != nil { + return nil, err + } + continue + } + + return member, nil + } +} + +func (d *ShapeDeserializer) nextAttrMember(ctx *deserCtx) *smithy.Schema { + attrs := ctx.startElem.Attr + for ctx.attrIndex < len(attrs) { + a := attrs[ctx.attrIndex] + ctx.attrIndex++ + + member := findAttrMember(ctx.schema, a.Name.Local) + if member == nil { + continue + } + + val := a.Value + d.currAttr = &val + return member + } + return nil +} + +func findAttrMember(schema *smithy.Schema, elemName string) *smithy.Schema { + if schema == nil { + return nil + } + + for _, m := range schema.Members() { + if _, ok := smithy.SchemaTrait[*traits.XMLAttribute](m); !ok { + continue + } + if strings.EqualFold(stripPrefix(xmlMemberName(m)), elemName) { + return m + } + } + return nil +} + +// ReadList implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadList(s *smithy.Schema) error { + _, flattened := smithy.SchemaTrait[*traits.XMLFlattened](s) + d.stack.Push(deserCtx{ + kind: ctxKindList, + schema: s, + flattened: flattened, + first: flattened, + }) + return nil +} + +// ReadListItem implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadListItem(_ *smithy.Schema) (bool, error) { + ctx := d.stack.Top() + if ctx.flattened { + return d.readFlatListItem() + } + return d.readWrappedListItem() +} + +func (d *ShapeDeserializer) readWrappedListItem() (bool, error) { + // the old version used WrapNodeDecoder on each item and didn't check its + // name (or for xmlName) so we're ignoring it too + _, ok, err := d.nextStart() + if err != nil { + return false, err + } + if !ok { + d.stack.Pop() + return false, nil + } + + return true, nil +} + +func (d *ShapeDeserializer) readFlatListItem() (bool, error) { + ctx := d.stack.Top() + if ctx.first { + ctx.first = false + return true, nil + } + + expectName := xmlMemberName(ctx.schema) + + for { + tok, err := d.token() + if err != nil { + return false, err + } + + switch t := tok.(type) { + case xml.StartElement: + if strings.EqualFold(t.Name.Local, expectName) { + return true, nil + } + + d.peeked = t + d.stack.Pop() + return false, nil + case xml.EndElement: + d.peeked = t + d.stack.Pop() + return false, nil + } + } +} + +// ReadMap implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadMap(s *smithy.Schema) error { + _, flattened := smithy.SchemaTrait[*traits.XMLFlattened](s) + d.stack.Push(deserCtx{ + kind: ctxKindMap, + schema: s, + flattened: flattened, + first: flattened, + }) + return nil +} + +// ReadMapKey implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadMapKey(ks *smithy.Schema) (string, bool, error) { + ctx := d.stack.Top() + if ctx.inMapEntry { + ctx.inMapEntry = false + if _, _, err := d.nextStart(); err != nil { + return "", false, err + } + } + + vs := ctx.schema.MapValue() + + if ctx.flattened { + return d.readFlatMapKey(ks, vs) + } + + return d.readWrappedMapKey(ks, vs) +} + +func (d *ShapeDeserializer) readWrappedMapKey(ks, vs *smithy.Schema) (string, bool, error) { + for { + start, a, err := d.nextStart() + if err != nil { + return "", false, err + } + if !a { + d.stack.Pop() + return "", false, nil + } + + // unlike lists the old codegen actually DID check that the element was + // named "entry", skipping if it wasn't + if strings.EqualFold(start.Name.Local, "entry") { + return d.readEntry(ks, vs) + } + + if err := d.skip(); err != nil { + return "", false, err + } + } +} + +func (d *ShapeDeserializer) readFlatMapKey(ks, vs *smithy.Schema) (string, bool, error) { + ctx := d.stack.Top() + if ctx.first { + ctx.first = false + return d.readEntry(ks, vs) + } + + expectName := xmlMemberName(ctx.schema) + for { + tok, err := d.token() + if err != nil { + return "", false, err + } + + switch t := tok.(type) { + case xml.StartElement: + if strings.EqualFold(t.Name.Local, expectName) { + return d.readEntry(ks, vs) + } + + d.peeked = t + d.stack.Pop() + return "", false, nil + case xml.EndElement: + d.peeked = t + d.stack.Pop() + return "", false, nil + } + } +} + +func (d *ShapeDeserializer) readEntry(ks, vs *smithy.Schema) (string, bool, error) { + ctx := d.stack.Top() + + kname := "key" + if xn, ok := smithy.SchemaDirectTrait[*traits.XMLName](ks); ok { + kname = xn.Name + } + + vname := "value" + if xn, ok := smithy.SchemaDirectTrait[*traits.XMLName](vs); ok { + vname = xn.Name + } + + var key string + for { + child, found, err := d.nextStart() + if err != nil { + return "", false, err + } + if !found { + break + } + + switch { + case strings.EqualFold(child.Name.Local, kname): + key, err = d.chardata() + if err != nil { + return "", false, err + } + case strings.EqualFold(child.Name.Local, vname): + ctx.inMapEntry = true + return key, true, nil + default: + if err := d.skip(); err != nil { + return "", false, err + } + } + } + + return key, true, nil +} + +// ReadNil implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadNil(_ *smithy.Schema) (bool, error) { + return false, nil +} + +// ReadBool implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadBool(_ *smithy.Schema, v *bool) error { + text, err := d.chardata() + if err != nil { + return err + } + + b, err := strconv.ParseBool(text) + if err != nil { + return fmt.Errorf("parse bool %q: %w", text, err) + } + + *v = b + return nil +} + +// ReadInt8 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadInt8(s *smithy.Schema, v *int8) error { + n, err := d.readInt(8) + if err != nil { + return err + } + + *v = int8(n) + return nil +} + +// ReadInt16 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadInt16(s *smithy.Schema, v *int16) error { + n, err := d.readInt(16) + if err != nil { + return err + } + + *v = int16(n) + return nil +} + +// ReadInt32 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadInt32(s *smithy.Schema, v *int32) error { + n, err := d.readInt(32) + if err != nil { + return err + } + + *v = int32(n) + return nil +} + +// ReadInt64 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadInt64(s *smithy.Schema, v *int64) error { + n, err := d.readInt(64) + if err != nil { + return err + } + + *v = n + return nil +} + +// ReadFloat32 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadFloat32(s *smithy.Schema, v *float32) error { + n, err := d.readFloat() + if err != nil { + return err + } + + *v = float32(n) + return nil +} + +// ReadFloat64 implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadFloat64(s *smithy.Schema, v *float64) error { + n, err := d.readFloat() + if err != nil { + return err + } + + *v = n + return nil +} + +// ReadString implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadString(_ *smithy.Schema, v *string) error { + text, err := d.chardata() + if err != nil { + return err + } + + *v = text + return nil +} + +// ReadTime implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadTime(schema *smithy.Schema, v *time.Time) error { + format := "date-time" + if t, ok := smithy.SchemaTrait[*traits.TimestampFormat](schema); ok { + format = t.Format + } + + text, err := d.chardata() + if err != nil { + return err + } + + switch format { + case "date-time": + t, err := smithytime.ParseDateTime(text) + if err != nil { + return err + } + *v = t + case "http-date": + t, err := smithytime.ParseHTTPDate(text) + if err != nil { + return err + } + *v = t + case "epoch-seconds": + n, err := strconv.ParseFloat(text, 64) + if err != nil { + return err + } + *v = smithytime.ParseEpochSeconds(n) + default: + return fmt.Errorf("unknown timestamp format: %s", format) + } + + return nil +} + +// ReadBlob implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadBlob(s *smithy.Schema, v *[]byte) error { + text, err := d.chardata() + if err != nil { + return err + } + + if text == "" { + return nil + } + + b, err := base64.StdEncoding.DecodeString(text) + if err != nil { + return fmt.Errorf("decode base64 blob: %w", err) + } + *v = b + return nil +} + +// ReadUnion implements [smithy.ShapeDeserializer]. +func (d *ShapeDeserializer) ReadUnion(s *smithy.Schema) (*smithy.Schema, error) { + if err := d.ensureStart(); err != nil { + return nil, err + } + + for { + start, ok, err := d.nextStart() + if err != nil { + return nil, err + } + if !ok { + return nil, nil + } + + member := findMember(s, start.Name.Local) + if member == nil { + if err := d.skip(); err != nil { + return nil, err + } + continue + } + + return member, nil + } +} + +// ReadDocument is unimplemented for XML. +func (d *ShapeDeserializer) ReadDocument(_ *smithy.Schema, _ *document.Value) error { + return fmt.Errorf("Document not supported") +} + +func (d *ShapeDeserializer) token() (xml.Token, error) { + if d.peeked != nil { + tok := d.peeked + d.peeked = nil + return tok, nil + } + + tok, err := d.dec.Token() + if err != nil { + return nil, err + } + + if start, ok := tok.(xml.StartElement); ok { + d.currStart = &start + } + + return tok, nil +} + +func (d *ShapeDeserializer) chardata() (string, error) { + if d.currAttr != nil { + v := *d.currAttr + d.currAttr = nil + return v, nil + } + + // a single "inner XML" node can be multiple xml.CharData so we need to + // accumulate them + var buf strings.Builder + + for { + tok, err := d.token() + if err != nil { + return "", err + } + + switch t := tok.(type) { + case xml.CharData: + buf.Write(t) + + // IMPORTANT: also consumes the closing tag AFTER the chardata, so + // future ReadWhatevers don't have to think about that + case xml.EndElement: + return buf.String(), nil + + default: + return "", fmt.Errorf("unexpected token %T", tok) + } + } +} + +// there is xml.Decoder.Skip but this is a special case to assume we already +// consumed the start element and to handle any peeked tokens +func (d *ShapeDeserializer) skip() error { + depth := 1 + for depth > 0 { + tok, err := d.token() + if err != nil { + return err + } + + switch tok.(type) { + case xml.StartElement: + depth++ + case xml.EndElement: + depth-- + } + } + + return nil +} + +func (d *ShapeDeserializer) nextStart() (xml.StartElement, bool, error) { + for { + tok, err := d.token() + if err != nil { + return xml.StartElement{}, false, err + } + + switch t := tok.(type) { + case xml.StartElement: + return t, true, nil + case xml.EndElement: // ie. the end of the struct/list/map + return xml.StartElement{}, false, nil + default: + continue + } + } +} + +// make sure we have the start element when we go into a struct/union context +func (d *ShapeDeserializer) ensureStart() error { + if d.currStart == nil { + if _, ok, err := d.nextStart(); err != nil { + return err + } else if !ok { + return fmt.Errorf("expected start element") + } + } + return nil +} + +func (d *ShapeDeserializer) readInt(bits int) (int64, error) { + text, err := d.chardata() + if err != nil { + return 0, err + } + + return strconv.ParseInt(text, 10, bits) +} + +func (d *ShapeDeserializer) readFloat() (float64, error) { + text, err := d.chardata() + if err != nil { + return 0, err + } + + switch { + case strings.EqualFold(text, "NaN"): + return math.NaN(), nil + case strings.EqualFold(text, "Infinity"): + return math.Inf(1), nil + case strings.EqualFold(text, "-Infinity"): + return math.Inf(-1), nil + default: + return strconv.ParseFloat(text, 64) + } +} + +func stripPrefix(name string) string { + if _, after, ok := strings.Cut(name, ":"); ok { + return after + } + return name +} + +// ReadBigInt is unimplemented and will return an error. +func (d *ShapeDeserializer) ReadBigInt(_ *smithy.Schema, _ *big.Int) error { + return fmt.Errorf("unimplemented") +} + +// ReadBigFloat is unimplemented and will return an error. +func (d *ShapeDeserializer) ReadBigFloat(_ *smithy.Schema, _ *big.Float) error { + return fmt.Errorf("unimplemented") +} diff --git a/transport/http/protocol/internal/xml/shape_serializer.go b/transport/http/protocol/internal/xml/shape_serializer.go new file mode 100644 index 000000000..1825c34af --- /dev/null +++ b/transport/http/protocol/internal/xml/shape_serializer.go @@ -0,0 +1,348 @@ +package xml + +import ( + "encoding/base64" + "math" + "math/big" + "strconv" + "time" + + "github.com/aws/smithy-go" + "github.com/aws/smithy-go/document" + "github.com/aws/smithy-go/internal/serde" + smithytime "github.com/aws/smithy-go/time" + "github.com/aws/smithy-go/traits" +) + +type serCtx struct { + kind ctxKind + schema *smithy.Schema + flat bool + + wrapperName string // open/close ename for struct and NON-flat list/map + itemName string // per-item element name for list items and map _entries_ + inMapEntry bool + + // we have to buffer inner xml for structs because they may have + // @xmlAttribute members written in any order + // + // @xmlNamespace is just resolved at flush time + w *writer + attrs []attr +} + +// ShapeSerializer implements marshaling of Smithy shapes to XML. +type ShapeSerializer struct { + w *writer + + stack serde.Stack[serCtx] + + unionNames []string + + opts ShapeSerializerOptions +} + +// ShapeSerializerOptions configures ShapeSerializer. +type ShapeSerializerOptions struct { + RootNamespaceURI string + RootNamespacePrefix string +} + +var _ smithy.ShapeSerializer = (*ShapeSerializer)(nil) + +// NewShapeSerializer creates a new ShapeSerializer. +func NewShapeSerializer(opts ...func(*ShapeSerializerOptions)) *ShapeSerializer { + o := ShapeSerializerOptions{} + for _, fn := range opts { + fn(&o) + } + return &ShapeSerializer{ + w: newWriter(), + stack: serde.NewStack[serCtx](), + opts: o, + } +} + +// Bytes returns the serialized XML bytes. +func (s *ShapeSerializer) Bytes() []byte { + return s.w.Bytes() +} + +// WriteInt8 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt8(schema *smithy.Schema, v int8) { + s.writeScalar(schema, strconv.FormatInt(int64(v), 10)) +} + +// WriteInt16 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt16(schema *smithy.Schema, v int16) { + s.writeScalar(schema, strconv.FormatInt(int64(v), 10)) +} + +// WriteInt32 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt32(schema *smithy.Schema, v int32) { + s.writeScalar(schema, strconv.FormatInt(int64(v), 10)) +} + +// WriteInt64 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteInt64(schema *smithy.Schema, v int64) { + s.writeScalar(schema, strconv.FormatInt(v, 10)) +} + +// WriteFloat32 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteFloat32(schema *smithy.Schema, v float32) { + s.writeScalar(schema, formatFloat(float64(v), 32)) +} + +// WriteFloat64 implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteFloat64(schema *smithy.Schema, v float64) { + s.writeScalar(schema, formatFloat(v, 64)) +} + +// WriteBool implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteBool(schema *smithy.Schema, v bool) { + s.writeScalar(schema, strconv.FormatBool(v)) +} + +// WriteString implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteString(schema *smithy.Schema, v string) { + s.writeScalar(schema, v) +} + +// WriteBigInt is unimplemented. +func (s *ShapeSerializer) WriteBigInt(_ *smithy.Schema, _ *big.Int) { + panic("BigInteger not supported") +} + +// WriteBigFloat is unimplemented. +func (s *ShapeSerializer) WriteBigFloat(_ *smithy.Schema, _ *big.Float) { + panic("BigDecimal not supported") +} + +// WriteBlob implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteBlob(schema *smithy.Schema, v []byte) { + if v == nil { + return + } + s.writeScalar(schema, base64.StdEncoding.EncodeToString(v)) +} + +// WriteTime implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteTime(schema *smithy.Schema, v time.Time) { + format := "date-time" + if t, ok := smithy.SchemaTrait[*traits.TimestampFormat](schema); ok { + format = t.Format + } + + switch format { + case "http-date": + s.writeScalar(schema, smithytime.FormatHTTPDate(v)) + case "epoch-seconds": + s.writeScalar(schema, strconv.FormatFloat(smithytime.FormatEpochSeconds(v), 'f', -1, 64)) + default: + s.writeScalar(schema, smithytime.FormatDateTime(v)) + } +} + +// WriteStruct implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteStruct(schema *smithy.Schema) { + name := s.structEname(schema) + + // @xmlNamespace on a target structure does NOT propagate through member + // references (the spec says nothing about inheritance, and the protocol + // tests confirm: XmlNamespaceNested has @xmlNamespace but the + // element does not get it). The exception is @httpPayload, where the + // member and its target represent the same XML element. + + ctx := serCtx{ + kind: ctxKindStruct, + wrapperName: name, + schema: schema, + w: s.w, + } + s.w = newWriter() + s.stack.Push(ctx) +} + +// CloseStruct implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseStruct() { + ctx := s.stack.Pop() + + var ns *traits.XMLNamespace + if isPayload(ctx.schema) { + ns, _ = smithy.SchemaTrait[*traits.XMLNamespace](ctx.schema) + } else { + ns, _ = smithy.SchemaDirectTrait[*traits.XMLNamespace](ctx.schema) + } + + // special case for the root struct where the service set a namespace + if ns == nil && s.stack.Len() == 0 && s.opts.RootNamespaceURI != "" { + ns = &traits.XMLNamespace{ + URI: s.opts.RootNamespaceURI, + Prefix: s.opts.RootNamespacePrefix, + } + } + + inner := s.w + s.w = ctx.w + s.w.writeStart(ctx.wrapperName, ns, ctx.attrs) + s.w.writeInner(inner) + s.w.writeEnd(ctx.wrapperName) +} + +// WriteUnion implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteUnion(schema, variant *smithy.Schema) { + name := s.ctxEname(schema) + s.w.writeStart(name, s.xmlns(schema), nil) + s.unionNames = append(s.unionNames, name) +} + +// CloseUnion implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseUnion() { + name := s.unionNames[len(s.unionNames)-1] + s.unionNames = s.unionNames[:len(s.unionNames)-1] + s.w.writeEnd(name) +} + +// WriteNil implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteNil(schema *smithy.Schema) { +} + +// WriteList implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteList(schema *smithy.Schema) { + _, flat := smithy.SchemaTrait[*traits.XMLFlattened](schema) + + ename := s.ename(schema) + iname := ename + if !flat { + ns, _ := smithy.SchemaDirectTrait[*traits.XMLNamespace](schema) + s.w.writeStart(ename, ns, nil) + iname = s.ename(schema.ListMember()) + } else { + ename = "" + } + + s.stack.Push(serCtx{ + kind: ctxKindList, + wrapperName: ename, + itemName: iname, + schema: schema, + flat: flat, + }) +} + +// CloseList implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseList() { + ctx := s.stack.Pop() + if !ctx.flat { + s.w.writeEnd(ctx.wrapperName) + } +} + +// WriteMap implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteMap(schema *smithy.Schema) { + _, flattened := smithy.SchemaTrait[*traits.XMLFlattened](schema) + + wrapperName := s.ename(schema) + itemName := "entry" + if flattened { + itemName = wrapperName + wrapperName = "" + } else { + ns, _ := smithy.SchemaDirectTrait[*traits.XMLNamespace](schema) + s.w.writeStart(wrapperName, ns, nil) + } + + s.stack.Push(serCtx{ + kind: ctxKindMap, + wrapperName: wrapperName, + itemName: itemName, + schema: schema, + flat: flattened, + }) +} + +// WriteKey implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) WriteKey(schema *smithy.Schema, k string) { + top := s.stack.Top() + if top == nil || top.kind != ctxKindMap { + return + } + + if top.inMapEntry { + s.w.writeEnd(top.itemName) + top.inMapEntry = false + } + + s.w.writeStart(top.itemName, nil, nil) + + keyName := s.ename(schema) + ns, _ := smithy.SchemaDirectTrait[*traits.XMLNamespace](top.schema.MapKey()) + s.w.writeStart(keyName, ns, nil) + s.w.writeChardata(k) + s.w.writeEnd(keyName) + + top.inMapEntry = true +} + +// CloseMap implements [smithy.ShapeSerializer]. +func (s *ShapeSerializer) CloseMap() { + ctx := s.stack.Pop() + + if ctx.inMapEntry { + s.w.writeEnd(ctx.itemName) + } + + if ctx.wrapperName != "" { + s.w.writeEnd(ctx.wrapperName) + } +} + +// WriteDocument is unimplemented for XML. +func (s *ShapeSerializer) WriteDocument(schema *smithy.Schema, v document.Value) { + panic("WriteDocument not supported for XML") +} + +func (s *ShapeSerializer) writeScalar(schema *smithy.Schema, v string) { + if s.bufferAttribute(schema, v) { + return + } + + name := s.ctxEname(schema) + s.w.writeStart(name, s.xmlns(schema), nil) + s.w.writeChardata(v) + s.w.writeEnd(name) +} + +func (s *ShapeSerializer) bufferAttribute(schema *smithy.Schema, v string) bool { + if _, ok := smithy.SchemaTrait[*traits.XMLAttribute](schema); !ok { + return false + } + + top := s.stack.Top() + if top == nil || top.kind != ctxKindStruct { + return false + } + + top.attrs = append(top.attrs, attr{ + name: s.ename(schema), + value: v, + }) + return true +} + +func formatFloat(v float64, bits int) string { + switch { + case math.IsNaN(v): + return "NaN" + case math.IsInf(v, 1): + return "Infinity" + case math.IsInf(v, -1): + return "-Infinity" + } + return strconv.FormatFloat(v, 'g', -1, bits) +} + +func isPayload(schema *smithy.Schema) bool { + _, ok := smithy.SchemaTrait[*traits.HTTPPayload](schema) + return ok +} diff --git a/transport/http/protocol/internal/xml/writer.go b/transport/http/protocol/internal/xml/writer.go new file mode 100644 index 000000000..aead0260b --- /dev/null +++ b/transport/http/protocol/internal/xml/writer.go @@ -0,0 +1,129 @@ +package xml + +import ( + "bytes" + "unicode/utf8" + + "github.com/aws/smithy-go/traits" +) + +type writer struct { + buf *bytes.Buffer +} + +func newWriter() *writer { + return &writer{ + buf: bytes.NewBuffer(nil), + } +} + +type attr struct { + name, value string +} + +func (w *writer) writeChardata(s string) { + escapeString(w.buf, s) +} + +func (w *writer) writeStart(name string, ns *traits.XMLNamespace, attrs []attr) { + w.buf.WriteByte('<') + escapeString(w.buf, name) + if ns != nil { + w.buf.WriteString(" xmlns") + if ns.Prefix != "" { + w.buf.WriteByte(':') + escapeString(w.buf, ns.Prefix) + } + w.buf.WriteString(`="`) + escapeString(w.buf, ns.URI) + w.buf.WriteByte('"') + } + for _, a := range attrs { + w.buf.WriteByte(' ') + escapeString(w.buf, a.name) + w.buf.WriteString(`="`) + escapeString(w.buf, a.value) + w.buf.WriteByte('"') + } + w.buf.WriteByte('>') +} + +func (w *writer) writeEnd(name string) { + w.buf.WriteString("') +} + +func (w *writer) writeInner(inner *writer) { + w.buf.Write(inner.buf.Bytes()) +} + +func (w *writer) Bytes() []byte { + return w.buf.Bytes() +} + +// copied from smithy-go/encoding/xml/escape.go + +var ( + escQuot = []byte(""") + escApos = []byte("'") + escAmp = []byte("&") + escLT = []byte("<") + escGT = []byte(">") + escTab = []byte(" ") + escNL = []byte(" ") + escCR = []byte(" ") + escFFFD = []byte("\uFFFD") + escNextLine = []byte("…") + escLS = []byte("
") +) + +func isInCharacterRange(r rune) bool { + return r == 0x09 || + r == 0x0A || + r == 0x0D || + r >= 0x20 && r <= 0xD7FF || + r >= 0xE000 && r <= 0xFFFD || + r >= 0x10000 && r <= 0x10FFFF +} + +func escapeString(w *bytes.Buffer, s string) { + var esc []byte + last := 0 + for i := 0; i < len(s); { + r, width := utf8.DecodeRuneInString(s[i:]) + i += width + switch r { + case '"': + esc = escQuot + case '\'': + esc = escApos + case '&': + esc = escAmp + case '<': + esc = escLT + case '>': + esc = escGT + case '\t': + esc = escTab + case '\n': + esc = escNL + case '\r': + esc = escCR + case '\u0085': + esc = escNextLine + case '\u2028': + esc = escLS + default: + if !isInCharacterRange(r) || (r == 0xFFFD && width == 1) { + esc = escFFFD + break + } + continue + } + w.WriteString(s[last : i-width]) + w.Write(esc) + last = i + } + w.WriteString(s[last:]) +} diff --git a/transport/http/protocol/restjson1/restjson1.go b/transport/http/protocol/restjson1/restjson1.go new file mode 100644 index 000000000..76ce6103a --- /dev/null +++ b/transport/http/protocol/restjson1/restjson1.go @@ -0,0 +1,219 @@ +package restjson1 + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + + "github.com/aws/smithy-go" + internalhttpbinding "github.com/aws/smithy-go/transport/http/protocol/internal/httpbinding" + internaljson "github.com/aws/smithy-go/transport/http/protocol/internal/json" + internales "github.com/aws/smithy-go/internal/eventstream" + smithyio "github.com/aws/smithy-go/io" + smithyhttp "github.com/aws/smithy-go/transport/http" +) + +// ProtocolOptions configures aws.protocols#restJson1. +type ProtocolOptions struct{} + +// New returns an instance of the aws.protocols#restJson1 protocol. +func New(_ *smithy.ServiceSchema, opts ...func(*ProtocolOptions)) *Protocol { + var o ProtocolOptions + for _, fn := range opts { + fn(&o) + } + return &Protocol{ + eventstream: &internales.Codec{ + Serializer: func() smithy.ShapeSerializer { return internaljson.NewShapeSerializer() }, + Deserializer: func(p []byte) smithy.ShapeDeserializer { return internaljson.NewShapeDeserializer(p) }, + ContentType: "application/json", + }, + } +} + +// Protocol implements aws.protocols#restJson1. +type Protocol struct { + eventstream *internales.Codec +} + +var _ smithyhttp.ClientProtocol = (*Protocol)(nil) + +// ID identifies the protocol. +func (*Protocol) ID() smithy.ShapeID { + return smithy.ShapeID{Namespace: "aws.protocols", Name: "restJson1"} +} + +// SerializeRequest serializes a request for restJson1. +func (p *Protocol) SerializeRequest( + ctx context.Context, + op *smithy.OperationSchema, + in smithy.Serializable, + req *smithyhttp.Request, +) error { + serializer, err := internalhttpbinding.NewShapeSerializer(op.Schema, req, internaljson.NewShapeSerializer(useJSONName)) + if err != nil { + return err + } + + if op.Input != nil { + in.Serialize(serializer) + } + + contentType := "application/json" + if op.IsInputEventStream() { + contentType = "application/vnd.amazon.eventstream" + } + if err := serializer.Build(in, contentType); err != nil { + return fmt.Errorf("build request: %w", err) + } + + return nil +} + +// DeserializeResponse deserializes a response for restJson1. +func (p *Protocol) DeserializeResponse( + ctx context.Context, + op *smithy.OperationSchema, + types *smithy.TypeRegistry, + resp *smithyhttp.Response, + out smithy.Deserializable, +) error { + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return p.deserializeError(types, resp) + } + + if op.Output == nil { + return nil + } + + // @httpPayload + @streaming = do not touch the body at all, it's the + // caller's problem + if so, ok := out.(smithy.StreamingOutput); ok { + so.SetPayloadStream(resp.Body) + deser := internalhttpbinding.NewShapeDeserializer(resp.Response, bd(nil), nil) + if err := out.Deserialize(deser); err != nil { + return &smithy.DeserializationError{Err: err} + } + return nil + } + + if op.IsOutputEventStream() { + if op.IsInputEventStream() { + if err := p.eventstream.RequireBidiHTTP2(resp.Proto, resp.ProtoMajor); err != nil { + return err + } + } + deser := internalhttpbinding.NewShapeDeserializer(resp.Response, bd(nil), nil) + if err := out.Deserialize(deser); err != nil { + return &smithy.DeserializationError{Err: err} + } + return nil + } + + payload, err := io.ReadAll(resp.Body) + if err != nil { + return &smithy.DeserializationError{Err: err} + } + + deser := internalhttpbinding.NewShapeDeserializer(resp.Response, bd(payload), payload) + if err := out.Deserialize(deser); err != nil { + return &smithy.DeserializationError{Err: err} + } + + return nil +} + +// HasInitialEventMessage is false for REST-style protocols with HTTP bindings. +func (*Protocol) HasInitialEventMessage() bool { + return false +} + +// SerializeEventMessage implements [smithyhttp.ClientProtocol]. +func (p *Protocol) SerializeEventMessage(schema, variant *smithy.Schema, v smithy.Serializable, w io.Writer) error { + return p.eventstream.SerializeEventMessage(schema, variant, v, w) +} + +// DeserializeEventMessage implements [smithyhttp.ClientProtocol]. +func (p *Protocol) DeserializeEventMessage(schema *smithy.Schema, types *smithy.TypeRegistry, r io.Reader) (smithy.Deserializable, error) { + return p.eventstream.DeserializeEventMessage(schema, types, r) +} + +// SerializeInitialRequest implements [smithyhttp.ClientProtocol]. +func (p *Protocol) SerializeInitialRequest(schema *smithy.Schema, v smithy.Serializable, w io.Writer) error { + return p.eventstream.SerializeInitialRequest(schema, v, w) +} + +// DeserializeInitialResponse implements [smithyhttp.ClientProtocol]. +func (p *Protocol) DeserializeInitialResponse(schema *smithy.Schema, r io.Reader, out smithy.Deserializable) error { + return p.eventstream.DeserializeInitialResponse(schema, r, out) +} + +func (p *Protocol) deserializeError(types *smithy.TypeRegistry, response *smithyhttp.Response) error { + var errorBuffer bytes.Buffer + if _, err := io.Copy(&errorBuffer, response.Body); err != nil { + return &smithy.DeserializationError{Err: fmt.Errorf("failed to copy error response body, %w", err)} + } + errorBody := bytes.NewReader(errorBuffer.Bytes()) + + errorCode := "UnknownError" + errorMessage := errorCode + + headerCode := response.Header.Get("X-Amzn-ErrorType") + + var buff [1024]byte + ringBuffer := smithyio.NewRingBuffer(buff[:]) + + body := io.TeeReader(errorBody, ringBuffer) + decoder := json.NewDecoder(body) + decoder.UseNumber() + bodyInfo, err := internaljson.GetProtocolErrorInfo(decoder) + if err != nil { + var snapshot bytes.Buffer + io.Copy(&snapshot, ringBuffer) + return &smithy.DeserializationError{ + Err: fmt.Errorf("failed to decode response body, %w", err), + Snapshot: snapshot.Bytes(), + } + } + + errorBody.Seek(0, io.SeekStart) + if typ, ok := internaljson.ResolveProtocolErrorType(headerCode, bodyInfo); ok { + errorCode = typ + } + if len(bodyInfo.Message) != 0 { + errorMessage = bodyInfo.Message + } + + errorCode = internaljson.SanitizeErrorCode(errorCode) + + perr, ok := types.DeserializableError(errorCode) + if !ok { + return &smithy.GenericAPIError{ + Code: errorCode, + Message: errorMessage, + } + } + + errorBody.Seek(0, io.SeekStart) + errorBytes, _ := io.ReadAll(errorBody) + + deser := internalhttpbinding.NewShapeDeserializer(response.Response, bd(errorBytes), errorBytes) + if err := perr.Deserialize(deser); err != nil { + return &smithy.DeserializationError{Err: err} + } + + return perr +} + +func bd(payload []byte) smithy.ShapeDeserializer { + if len(payload) == 0 { + payload = []byte("{}") + } + return internaljson.NewShapeDeserializer(payload, useJSONName) +} + +func useJSONName(o *internaljson.Options) { + o.UseJSONName = true +} diff --git a/transport/http/protocol/restxml/restxml.go b/transport/http/protocol/restxml/restxml.go new file mode 100644 index 000000000..e4bc07148 --- /dev/null +++ b/transport/http/protocol/restxml/restxml.go @@ -0,0 +1,221 @@ +package restxml + +import ( + "bytes" + "context" + "fmt" + "io" + + "github.com/aws/smithy-go" + internalhttpbinding "github.com/aws/smithy-go/transport/http/protocol/internal/httpbinding" + internalxml "github.com/aws/smithy-go/transport/http/protocol/internal/xml" + internales "github.com/aws/smithy-go/internal/eventstream" + "github.com/aws/smithy-go/traits" + smithyhttp "github.com/aws/smithy-go/transport/http" +) + +// Protocol implements aws.protocols#restXml. +type Protocol struct { + eventstream *internales.Codec + + seropts func(*internalxml.ShapeSerializerOptions) +} + +// ProtocolOptions configures aws.protocols#restXml. +type ProtocolOptions struct{} + +var _ smithyhttp.ClientProtocol = (*Protocol)(nil) + +// New returns an instance of the aws.protocols#restXml protocol. If the +// service schema carries an xmlNamespace trait, it is applied as the root +// xmlns attribute on serialized request bodies. +func New(service *smithy.ServiceSchema, opts ...func(*ProtocolOptions)) *Protocol { + var o ProtocolOptions + for _, fn := range opts { + fn(&o) + } + var xmlOpts func(*internalxml.ShapeSerializerOptions) + if ns, ok := smithy.SchemaTrait[*traits.XMLNamespace](service.Schema); ok { + xmlOpts = func(o *internalxml.ShapeSerializerOptions) { + o.RootNamespaceURI = ns.URI + o.RootNamespacePrefix = ns.Prefix + } + } + + return &Protocol{ + eventstream: &internales.Codec{ + Serializer: func() smithy.ShapeSerializer { + if xmlOpts != nil { + return internalxml.NewShapeSerializer(xmlOpts) + } + return internalxml.NewShapeSerializer() + }, + Deserializer: func(p []byte) smithy.ShapeDeserializer { return internalxml.NewShapeDeserializer(p) }, + ContentType: "application/xml", + }, + seropts: xmlOpts, + } +} + +// ID identifies the protocol. +func (*Protocol) ID() smithy.ShapeID { + return smithy.ShapeID{Namespace: "aws.protocols", Name: "restXml"} +} + +// HasInitialEventMessage implements [smithyhttp.ClientProtocol]. +func (*Protocol) HasInitialEventMessage() bool { + return false +} + +// SerializeEventMessage implements [smithyhttp.ClientProtocol]. +func (p *Protocol) SerializeEventMessage(schema, variant *smithy.Schema, v smithy.Serializable, w io.Writer) error { + return p.eventstream.SerializeEventMessage(schema, variant, v, w) +} + +// DeserializeEventMessage implements [smithyhttp.ClientProtocol]. +func (p *Protocol) DeserializeEventMessage(schema *smithy.Schema, types *smithy.TypeRegistry, r io.Reader) (smithy.Deserializable, error) { + return p.eventstream.DeserializeEventMessage(schema, types, r) +} + +// SerializeInitialRequest implements [smithyhttp.ClientProtocol]. +func (p *Protocol) SerializeInitialRequest(schema *smithy.Schema, v smithy.Serializable, w io.Writer) error { + return p.eventstream.SerializeInitialRequest(schema, v, w) +} + +// DeserializeInitialResponse implements [smithyhttp.ClientProtocol]. +func (p *Protocol) DeserializeInitialResponse(schema *smithy.Schema, r io.Reader, out smithy.Deserializable) error { + return p.eventstream.DeserializeInitialResponse(schema, r, out) +} + +// SerializeRequest serializes a request for restxml. +func (p *Protocol) SerializeRequest( + ctx context.Context, + op *smithy.OperationSchema, + in smithy.Serializable, + req *smithyhttp.Request, +) error { + serializer, err := internalhttpbinding.NewShapeSerializer(op.Schema, req, p.newSerializer()) + if err != nil { + return err + } + + if op.Input != nil { + in.Serialize(serializer) + } + + contentType := "application/xml" + if op.IsInputEventStream() { + contentType = "application/vnd.amazon.eventstream" + } + if err := serializer.Build(in, contentType); err != nil { + return fmt.Errorf("build request: %w", err) + } + + return nil +} + +// DeserializeResponse deserializes a response for restXml. +func (p *Protocol) DeserializeResponse( + ctx context.Context, + op *smithy.OperationSchema, + types *smithy.TypeRegistry, + resp *smithyhttp.Response, + out smithy.Deserializable, +) error { + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return p.deserializeError(types, resp) + } + + if op.Output == nil { + return nil + } + + if so, ok := out.(smithy.StreamingOutput); ok { + so.SetPayloadStream(resp.Body) + deser := internalhttpbinding.NewShapeDeserializer(resp.Response, deser(nil, op.Output), nil) + if err := out.Deserialize(deser); err != nil { + return &smithy.DeserializationError{Err: err} + } + return nil + } + + if op.IsOutputEventStream() { + if op.IsInputEventStream() { + if err := p.eventstream.RequireBidiHTTP2(resp.Proto, resp.ProtoMajor); err != nil { + return err + } + } + deser := internalhttpbinding.NewShapeDeserializer(resp.Response, deser(nil, op.Output), nil) + if err := out.Deserialize(deser); err != nil { + return &smithy.DeserializationError{Err: err} + } + return nil + } + + payload, err := io.ReadAll(resp.Body) + if err != nil { + return &smithy.DeserializationError{Err: err} + } + + deser := internalhttpbinding.NewShapeDeserializer(resp.Response, deser(payload, op.Output), payload) + if err := out.Deserialize(deser); err != nil { + return &smithy.DeserializationError{Err: err} + } + + return nil +} + +func (p *Protocol) newSerializer() *internalxml.ShapeSerializer { + if p.seropts != nil { + return internalxml.NewShapeSerializer(p.seropts) + } + return internalxml.NewShapeSerializer() +} + +func (p *Protocol) deserializeError(types *smithy.TypeRegistry, resp *smithyhttp.Response) error { + var buf bytes.Buffer + if _, err := io.Copy(&buf, resp.Body); err != nil { + return &smithy.DeserializationError{Err: fmt.Errorf("read error response body: %w", err)} + } + payload := buf.Bytes() + + errorCode, errorMessage, errorBody, err := internalxml.GetProtocolErrorInfo(payload) + if err != nil { + return &smithy.DeserializationError{Err: err} + } + + perr, ok := types.DeserializableError(errorCode) + if !ok { + return &smithy.GenericAPIError{ + Code: errorCode, + Message: errorMessage, + } + } + + if len(errorBody) > 0 { + deser := internalhttpbinding.NewShapeDeserializer(resp.Response, internalxml.NewShapeDeserializer(errorBody), payload) + if err := perr.Deserialize(deser); err != nil { + return &smithy.DeserializationError{Err: err} + } + } + + return perr +} + +// returns a ShapeDeserializer over the (possibly empty) payload, wrapped in +// a synthetic outer element whose name matches the operation output shape so +// the schema-serde XML deserializer can open it uniformly +func deser(payload []byte, out *smithy.Schema) smithy.ShapeDeserializer { + if len(payload) != 0 { + return internalxml.NewShapeDeserializer(payload) + } + + name := "Response" + if out != nil { + if n := out.ID().Name; n != "" { + name = n + } + } + payload = []byte("<" + name + ">") + return internalxml.NewShapeDeserializer(payload) +} diff --git a/transport/http/protocol/rpcv2/rpcv2.go b/transport/http/protocol/rpcv2/rpcv2.go new file mode 100644 index 000000000..5e808e2dc --- /dev/null +++ b/transport/http/protocol/rpcv2/rpcv2.go @@ -0,0 +1,245 @@ +package rpcv2 + +import ( + "bytes" + "context" + "fmt" + "io" + "net/http" + + "github.com/aws/smithy-go" + internales "github.com/aws/smithy-go/internal/eventstream" + internalerrors "github.com/aws/smithy-go/internal/errors" + smithyio "github.com/aws/smithy-go/io" + "github.com/aws/smithy-go/middleware" + internalcbor "github.com/aws/smithy-go/transport/http/protocol/internal/cbor" + "github.com/aws/smithy-go/traits" + smithyhttp "github.com/aws/smithy-go/transport/http" +) + +// Protocol implements an RPC v2 protocol. +// +// RPCv2 protocol family: +// - CBOR: https://smithy.io/2.0/additional-specs/protocols/smithy-rpc-v2.html +type Protocol struct { + queryCompatible bool + serviceName string + + eventstream *internales.Codec +} + +var _ smithyhttp.ClientProtocol = (*Protocol)(nil) + +// ProtocolOptions configures smithy.protocols#rpcv2Cbor. +type ProtocolOptions struct{} + +// NewCBOR returns an instance of the smithy.protocols#rpcv2Cbor protocol. +func NewCBOR(service *smithy.ServiceSchema, opts ...func(*ProtocolOptions)) *Protocol { + var o ProtocolOptions + for _, fn := range opts { + fn(&o) + } + _, qc := smithy.SchemaTrait[*traits.AWSQueryCompatible](service.Schema) + return &Protocol{ + queryCompatible: qc, + serviceName: service.Schema.ID().Name, + eventstream: &internales.Codec{ + Serializer: func() smithy.ShapeSerializer { return internalcbor.NewShapeSerializer() }, + Deserializer: func(p []byte) smithy.ShapeDeserializer { return internalcbor.NewShapeDeserializer(p) }, + ContentType: "application/cbor", + }, + } +} + +// ID identifies the protocol. +func (p *Protocol) ID() smithy.ShapeID { + return smithy.ShapeID{Namespace: "smithy.protocols", Name: "rpcv2Cbor"} +} + +// SerializeRequest serializes a request for rpcv2Cbor. +func (p *Protocol) SerializeRequest( + ctx context.Context, + schema *smithy.OperationSchema, + in smithy.Serializable, + req *smithyhttp.Request, +) error { + req.Method = http.MethodPost + req.URL.Path = fmt.Sprintf("/service/%s/operation/%s", + p.serviceName, middleware.GetOperationName(ctx)) + req.Header.Set("Smithy-Protocol", "rpc-v2-cbor") + req.Header.Set("Accept", "application/cbor") + if p.queryCompatible { + req.Header.Set("X-Amzn-Query-Mode", "true") + } + + if schema.IsInputEventStream() { + req.Header.Set("Content-Type", "application/vnd.amazon.eventstream") + return nil + } + + if schema.Input == nil { + return nil + } + + ss := internalcbor.NewShapeSerializer() + in.Serialize(ss) + + payload := ss.Bytes() + if len(payload) == 0 { + return nil + } + + if ss.IsUnitShape() { + return nil + } + + req.Header.Set("Content-Type", "application/cbor") + + sreq, err := req.SetStream(bytes.NewReader(payload)) + if err != nil { + return fmt.Errorf("set stream: %w", err) + } + + *req = *sreq + return nil +} + +// DeserializeResponse deserializes a response for rpcv2Cbor. +func (p *Protocol) DeserializeResponse( + ctx context.Context, + schema *smithy.OperationSchema, + types *smithy.TypeRegistry, + resp *smithyhttp.Response, + out smithy.Deserializable, +) error { + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return p.deserializeError(types, resp) + } + + if schema.IsOutputEventStream() { + if schema.IsInputEventStream() { + if err := p.eventstream.RequireBidiHTTP2(resp.Proto, resp.ProtoMajor); err != nil { + return err + } + } + return nil + } + + payload, err := io.ReadAll(resp.Body) + if err != nil { + return &smithy.DeserializationError{Err: err} + } + + if len(payload) == 0 { + return nil + } + + sd := internalcbor.NewShapeDeserializer(payload) + if err := out.Deserialize(sd); err != nil { + return &smithy.DeserializationError{Err: err} + } + + return nil +} + +// HasInitialEventMessage is true because this is an RPC protocol. +func (*Protocol) HasInitialEventMessage() bool { + return true +} + +// SerializeEventMessage implements [smithyhttp.ClientProtocol]. +func (p *Protocol) SerializeEventMessage(schema, variant *smithy.Schema, v smithy.Serializable, w io.Writer) error { + return p.eventstream.SerializeEventMessage(schema, variant, v, w) +} + +// DeserializeEventMessage implements [smithyhttp.ClientProtocol]. +func (p *Protocol) DeserializeEventMessage(schema *smithy.Schema, types *smithy.TypeRegistry, r io.Reader) (smithy.Deserializable, error) { + return p.eventstream.DeserializeEventMessage(schema, types, r) +} + +// SerializeInitialRequest implements [smithyhttp.ClientProtocol]. +func (p *Protocol) SerializeInitialRequest(schema *smithy.Schema, v smithy.Serializable, w io.Writer) error { + return p.eventstream.SerializeInitialRequest(schema, v, w) +} + +// DeserializeInitialResponse implements [smithyhttp.ClientProtocol]. +func (p *Protocol) DeserializeInitialResponse(schema *smithy.Schema, r io.Reader, out smithy.Deserializable) error { + return p.eventstream.DeserializeInitialResponse(schema, r, out) +} + +func (p *Protocol) deserializeError(types *smithy.TypeRegistry, response *smithyhttp.Response) error { + var errorBuffer bytes.Buffer + if _, err := io.Copy(&errorBuffer, response.Body); err != nil { + return &smithy.DeserializationError{Err: fmt.Errorf("failed to copy error response body, %w", err)} + } + errorBody := errorBuffer.Bytes() + + errorCode := "UnknownError" + errorMessage := errorCode + + var buff [1024]byte + ringBuffer := smithyio.NewRingBuffer(buff[:]) + + body := io.TeeReader(bytes.NewReader(errorBody), ringBuffer) + bodyBytes, err := io.ReadAll(body) + if err != nil { + var snapshot bytes.Buffer + io.Copy(&snapshot, ringBuffer) + return &smithy.DeserializationError{ + Err: fmt.Errorf("failed to read response body, %w", err), + Snapshot: snapshot.Bytes(), + } + } + + bodyType, bodyMessage, err := internalcbor.GetProtocolErrorInfo(bodyBytes) + if err != nil { + var snapshot bytes.Buffer + io.Copy(&snapshot, ringBuffer) + return &smithy.DeserializationError{ + Err: fmt.Errorf("failed to decode response body, %w", err), + Snapshot: snapshot.Bytes(), + } + } + + if bodyType != "" { + errorCode = bodyType + } + if bodyMessage != "" { + errorMessage = bodyMessage + } + + errorCode = internalerrors.SanitizeErrorCode(errorCode) + + var queryCode string + var queryFault smithy.ErrorFault + if p.queryCompatible { + queryHeader := response.Header.Get("X-Amzn-Query-Error") + queryCode, queryFault = internalerrors.ParseQueryError(queryHeader) + } + + perr, ok := types.DeserializableError(errorCode) + if !ok { + code := errorCode + if queryCode != "" { + code = queryCode + } + return &smithy.GenericAPIError{ + Code: code, + Message: errorMessage, + Fault: queryFault, + } + } + + if len(bodyBytes) > 0 { + deser := internalcbor.NewShapeDeserializer(bodyBytes) + if err := perr.Deserialize(deser); err != nil { + return &smithy.DeserializationError{Err: err} + } + } + + if queryCode != "" { + internalerrors.SetErrorCodeOverride(perr, queryCode) + } + + return perr +} diff --git a/type_registry.go b/type_registry.go new file mode 100644 index 000000000..3c4e02a18 --- /dev/null +++ b/type_registry.go @@ -0,0 +1,70 @@ +package smithy + +import ( + "strings" +) + +// TypeRegistry creates an instance of a type based on its Smithy IDL shape ID. +// +// Generated clients have an exported package-level registry (named +// TypeRegistry) that holds all structure types for the service. +type TypeRegistry struct { + Entries map[string]*TypeRegistryEntry +} + +// RegistryEntry creates a type registry entry. +func RegistryEntry[T any](schema *Schema) *TypeRegistryEntry { + return &TypeRegistryEntry{ + Schema: schema, + New: func() any { + return new(T) + }, + } +} + +// DeserializableError provides an instance of a deserializable error structure +// for a given shape ID. +// +// The ID is given as a string here since this will be called in a context where +// a shape ID is a discriminator read in from some wire payload. +func (t *TypeRegistry) DeserializableError(id string) (DeserializableError, bool) { + return typeRegistryLookup[DeserializableError](t, id) +} + +// LookupEntry returns the registry entry for the given shape ID. +func (t *TypeRegistry) LookupEntry(id string) (*TypeRegistryEntry, bool) { + entry, ok := t.Entries[id] + if !ok { + entry, ok = t.lookupShortName(id) + } + return entry, ok +} + +// TypeRegistryEntry holds the schema and constructor for a registered shape. +type TypeRegistryEntry struct { + Schema *Schema + New func() any +} + +func (t *TypeRegistry) lookupShortName(id string) (*TypeRegistryEntry, bool) { + for key, e := range t.Entries { + if idx := strings.Index(key, "#"); idx != -1 && key[idx+1:] == id { + return e, true + } + } + return nil, false +} + +func typeRegistryLookup[T any](t *TypeRegistry, id string) (T, bool) { + entry, ok := t.Entries[id] + if !ok { + entry, ok = t.lookupShortName(id) + } + if !ok { + var v T + return v, false + } + + v, ok := entry.New().(T) + return v, ok +}