From 0da3e3e38b20489f2565bf8d191589f5c2a775fb Mon Sep 17 00:00:00 2001 From: Sam Tunnicliffe Date: Tue, 8 Dec 2020 08:48:25 +0000 Subject: [PATCH 1/4] [JAVA-2704] Remove protocol v5 beta status, add v6-beta --- .../java/com/datastax/driver/core/BatchStatement.java | 1 + .../java/com/datastax/driver/core/BoundStatement.java | 1 + .../main/java/com/datastax/driver/core/CodecUtils.java | 5 +++++ .../main/java/com/datastax/driver/core/Connection.java | 7 +++++-- .../src/main/java/com/datastax/driver/core/Frame.java | 3 +++ .../main/java/com/datastax/driver/core/ProtocolEvent.java | 1 + .../java/com/datastax/driver/core/ProtocolFeature.java | 2 +- .../java/com/datastax/driver/core/ProtocolVersion.java | 7 ++++--- .../java/com/datastax/driver/core/RegularStatement.java | 1 + .../src/main/java/com/datastax/driver/core/Requests.java | 4 ++++ .../src/main/java/com/datastax/driver/core/Responses.java | 2 ++ .../java/com/datastax/driver/core/StreamIdGenerator.java | 1 + .../com/datastax/driver/core/ProtocolBetaVersionTest.java | 8 ++++---- .../driver/core/ProtocolVersionRenegotiationTest.java | 8 ++++---- .../java/com/datastax/driver/core/SegmentBuilderTest.java | 2 +- .../datastax/driver/core/SegmentToFrameDecoderTest.java | 6 +++--- 16 files changed, 41 insertions(+), 18 deletions(-) diff --git a/driver-core/src/main/java/com/datastax/driver/core/BatchStatement.java b/driver-core/src/main/java/com/datastax/driver/core/BatchStatement.java index e5fc175fe62..e2b7a805483 100644 --- a/driver-core/src/main/java/com/datastax/driver/core/BatchStatement.java +++ b/driver-core/src/main/java/com/datastax/driver/core/BatchStatement.java @@ -220,6 +220,7 @@ public int requestSizeInBytes(ProtocolVersion protocolVersion, CodecRegistry cod case V3: case V4: case V5: + case V6: size += CBUtil.sizeOfConsistencyLevel(getConsistencyLevel()); size += QueryFlag.serializedSize(protocolVersion); // Serial CL and default timestamp also depend on session-level defaults (QueryOptions). diff --git a/driver-core/src/main/java/com/datastax/driver/core/BoundStatement.java b/driver-core/src/main/java/com/datastax/driver/core/BoundStatement.java index 9b1c8d0a3d2..01c39dfc232 100644 --- a/driver-core/src/main/java/com/datastax/driver/core/BoundStatement.java +++ b/driver-core/src/main/java/com/datastax/driver/core/BoundStatement.java @@ -321,6 +321,7 @@ public int requestSizeInBytes(ProtocolVersion protocolVersion, CodecRegistry cod case V3: case V4: case V5: + case V6: size += CBUtil.sizeOfConsistencyLevel(getConsistencyLevel()); size += QueryFlag.serializedSize(protocolVersion); if (wrapper.values.length > 0) { diff --git a/driver-core/src/main/java/com/datastax/driver/core/CodecUtils.java b/driver-core/src/main/java/com/datastax/driver/core/CodecUtils.java index 94295a0bb14..afaa7176bca 100644 --- a/driver-core/src/main/java/com/datastax/driver/core/CodecUtils.java +++ b/driver-core/src/main/java/com/datastax/driver/core/CodecUtils.java @@ -64,6 +64,7 @@ public static int readSize(ByteBuffer input, ProtocolVersion version) { case V3: case V4: case V5: + case V6: return input.getInt(); default: throw version.unsupported(); @@ -92,6 +93,7 @@ public static void writeSize(ByteBuffer output, int size, ProtocolVersion versio case V3: case V4: case V5: + case V6: output.putInt(size); break; default: @@ -131,6 +133,7 @@ public static void writeValue(ByteBuffer output, ByteBuffer value, ProtocolVersi case V3: case V4: case V5: + case V6: if (value == null) { output.putInt(-1); } else { @@ -217,6 +220,7 @@ private static int sizeOfCollectionSize(ProtocolVersion version) { case V3: case V4: case V5: + case V6: return 4; default: throw version.unsupported(); @@ -237,6 +241,7 @@ private static int sizeOfValue(ByteBuffer value, ProtocolVersion version) { case V3: case V4: case V5: + case V6: return value == null ? 4 : 4 + value.remaining(); default: throw version.unsupported(); diff --git a/driver-core/src/main/java/com/datastax/driver/core/Connection.java b/driver-core/src/main/java/com/datastax/driver/core/Connection.java index 92861dc9fe6..d953130cc6b 100644 --- a/driver-core/src/main/java/com/datastax/driver/core/Connection.java +++ b/driver-core/src/main/java/com/datastax/driver/core/Connection.java @@ -394,6 +394,7 @@ public ListenableFuture apply(Message.Response response) throws Exception case V3: case V4: case V5: + case V6: return authenticateV2(authenticator, protocolVersion, initExecutor); default: throw defunct(protocolVersion.unsupported()); @@ -1659,6 +1660,8 @@ private static class Initializer extends ChannelInitializer { new Message.ProtocolEncoder(ProtocolVersion.V4); private static final Message.ProtocolEncoder messageEncoderV5 = new Message.ProtocolEncoder(ProtocolVersion.V5); + private static final Message.ProtocolEncoder messageEncoderV6 = + new Message.ProtocolEncoder(ProtocolVersion.V6); private static final Frame.Encoder frameEncoder = new Frame.Encoder(); private final ProtocolVersion protocolVersion; @@ -1756,6 +1759,8 @@ private Message.ProtocolEncoder messageEncoderFor(ProtocolVersion version) { return messageEncoderV4; case V5: return messageEncoderV5; + case V6: + return messageEncoderV6; default: throw new DriverInternalError("Unsupported protocol version " + protocolVersion); } @@ -1768,8 +1773,6 @@ private Message.ProtocolEncoder messageEncoderFor(ProtocolVersion version) { * v5. */ void switchToV5Framing() { - assert factory.protocolVersion.compareTo(ProtocolVersion.V5) >= 0; - // We want to do this on the event loop, to make sure it doesn't race with incoming requests assert channel.eventLoop().inEventLoop(); diff --git a/driver-core/src/main/java/com/datastax/driver/core/Frame.java b/driver-core/src/main/java/com/datastax/driver/core/Frame.java index f0480e8efa1..392e09c7bf0 100644 --- a/driver-core/src/main/java/com/datastax/driver/core/Frame.java +++ b/driver-core/src/main/java/com/datastax/driver/core/Frame.java @@ -106,6 +106,7 @@ private static int readStreamId(ByteBuf fullFrame, ProtocolVersion version) { case V3: case V4: case V5: + case V6: return fullFrame.readShort(); default: throw version.unsupported(); @@ -156,6 +157,7 @@ static int lengthFor(ProtocolVersion version) { case V3: case V4: case V5: + case V6: return 9; default: throw version.unsupported(); @@ -174,6 +176,7 @@ public void encodeInto(ByteBuf destination) { case V3: case V4: case V5: + case V6: destination.writeShort(streamId); break; default: diff --git a/driver-core/src/main/java/com/datastax/driver/core/ProtocolEvent.java b/driver-core/src/main/java/com/datastax/driver/core/ProtocolEvent.java index e72a11e7cdd..eb841598e3d 100644 --- a/driver-core/src/main/java/com/datastax/driver/core/ProtocolEvent.java +++ b/driver-core/src/main/java/com/datastax/driver/core/ProtocolEvent.java @@ -155,6 +155,7 @@ static SchemaChange deserializeEvent(ByteBuf bb, ProtocolVersion version) { case V3: case V4: case V5: + case V6: change = CBUtil.readEnumValue(Change.class, bb); targetType = CBUtil.readEnumValue(SchemaElement.class, bb); targetKeyspace = CBUtil.readString(bb); diff --git a/driver-core/src/main/java/com/datastax/driver/core/ProtocolFeature.java b/driver-core/src/main/java/com/datastax/driver/core/ProtocolFeature.java index 57f9d6f79b9..fdbdbbe6ef6 100644 --- a/driver-core/src/main/java/com/datastax/driver/core/ProtocolFeature.java +++ b/driver-core/src/main/java/com/datastax/driver/core/ProtocolFeature.java @@ -42,7 +42,7 @@ enum ProtocolFeature { boolean isSupportedBy(ProtocolVersion version) { switch (this) { case PREPARED_METADATA_CHANGES: - return version == ProtocolVersion.V5; + return version.compareTo(ProtocolVersion.V5) >= 0; case CUSTOM_PAYLOADS: return version.compareTo(ProtocolVersion.V4) >= 0; case CLIENT_TIMESTAMPS: diff --git a/driver-core/src/main/java/com/datastax/driver/core/ProtocolVersion.java b/driver-core/src/main/java/com/datastax/driver/core/ProtocolVersion.java index e165c6b17e7..10c696f638b 100644 --- a/driver-core/src/main/java/com/datastax/driver/core/ProtocolVersion.java +++ b/driver-core/src/main/java/com/datastax/driver/core/ProtocolVersion.java @@ -26,13 +26,14 @@ public enum ProtocolVersion { V2("2.0.0", 2, V1), V3("2.1.0", 3, V2), V4("2.2.0", 4, V3), - V5("3.10.0", 5, V4); + V5("3.10.0", 5, V4), + V6("4.0.0", 6, V5); /** The most recent protocol version supported by the driver. */ - public static final ProtocolVersion NEWEST_SUPPORTED = V4; + public static final ProtocolVersion NEWEST_SUPPORTED = V5; /** The most recent beta protocol version supported by the driver. */ - public static final ProtocolVersion NEWEST_BETA = V5; + public static final ProtocolVersion NEWEST_BETA = V6; private final VersionNumber minCassandraVersion; diff --git a/driver-core/src/main/java/com/datastax/driver/core/RegularStatement.java b/driver-core/src/main/java/com/datastax/driver/core/RegularStatement.java index 27f08226e60..3a9e8fe90d7 100644 --- a/driver-core/src/main/java/com/datastax/driver/core/RegularStatement.java +++ b/driver-core/src/main/java/com/datastax/driver/core/RegularStatement.java @@ -194,6 +194,7 @@ public int requestSizeInBytes(ProtocolVersion protocolVersion, CodecRegistry cod case V3: case V4: case V5: + case V6: size += CBUtil.sizeOfConsistencyLevel(getConsistencyLevel()); size += QueryFlag.serializedSize(protocolVersion); if (hasValues()) { diff --git a/driver-core/src/main/java/com/datastax/driver/core/Requests.java b/driver-core/src/main/java/com/datastax/driver/core/Requests.java index 3cd15773eb8..c9d8110c758 100644 --- a/driver-core/src/main/java/com/datastax/driver/core/Requests.java +++ b/driver-core/src/main/java/com/datastax/driver/core/Requests.java @@ -388,6 +388,7 @@ void encode(ByteBuf dest, ProtocolVersion version) { case V3: case V4: case V5: + case V6: CBUtil.writeConsistencyLevel(consistency, dest); QueryFlag.serialize(flags, dest, version); if (flags.contains(QueryFlag.VALUES)) { @@ -425,6 +426,7 @@ int encodedSize(ProtocolVersion version) { case V3: case V4: case V5: + case V6: int size = 0; size += CBUtil.sizeOfConsistencyLevel(consistency); size += QueryFlag.serializedSize(version); @@ -596,6 +598,7 @@ void encode(ByteBuf dest, ProtocolVersion version) { case V3: case V4: case V5: + case V6: CBUtil.writeConsistencyLevel(consistency, dest); QueryFlag.serialize(flags, dest, version); if (flags.contains(QueryFlag.SERIAL_CONSISTENCY)) @@ -616,6 +619,7 @@ int encodedSize(ProtocolVersion version) { case V3: case V4: case V5: + case V6: int size = 0; size += CBUtil.sizeOfConsistencyLevel(consistency); size += QueryFlag.serializedSize(version); diff --git a/driver-core/src/main/java/com/datastax/driver/core/Responses.java b/driver-core/src/main/java/com/datastax/driver/core/Responses.java index b0245f2bfff..d9402f5b189 100644 --- a/driver-core/src/main/java/com/datastax/driver/core/Responses.java +++ b/driver-core/src/main/java/com/datastax/driver/core/Responses.java @@ -605,6 +605,7 @@ private Metadata decodeResultMetadata( case V3: case V4: case V5: + case V6: return Rows.Metadata.decode(body, version, codecRegistry); default: throw version.unsupported(); @@ -679,6 +680,7 @@ public Result decode( case V3: case V4: case V5: + case V6: change = CBUtil.readEnumValue(Change.class, body); targetType = CBUtil.readEnumValue(SchemaElement.class, body); targetKeyspace = CBUtil.readString(body); diff --git a/driver-core/src/main/java/com/datastax/driver/core/StreamIdGenerator.java b/driver-core/src/main/java/com/datastax/driver/core/StreamIdGenerator.java index b7f613d569f..182c4ac3940 100644 --- a/driver-core/src/main/java/com/datastax/driver/core/StreamIdGenerator.java +++ b/driver-core/src/main/java/com/datastax/driver/core/StreamIdGenerator.java @@ -47,6 +47,7 @@ private static int streamIdSizeFor(ProtocolVersion version) { case V3: case V4: case V5: + case V6: return 2; default: throw version.unsupported(); diff --git a/driver-core/src/test/java/com/datastax/driver/core/ProtocolBetaVersionTest.java b/driver-core/src/test/java/com/datastax/driver/core/ProtocolBetaVersionTest.java index 96c84706608..55b743775a8 100644 --- a/driver-core/src/test/java/com/datastax/driver/core/ProtocolBetaVersionTest.java +++ b/driver-core/src/test/java/com/datastax/driver/core/ProtocolBetaVersionTest.java @@ -16,7 +16,7 @@ package com.datastax.driver.core; import static com.datastax.driver.core.ProtocolVersion.V4; -import static com.datastax.driver.core.ProtocolVersion.V5; +import static com.datastax.driver.core.ProtocolVersion.V6; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -85,7 +85,7 @@ public void should_not_initialize_when_beta_flag_is_set_and_version_explicitly_r /** * Verifies that the driver CANNOT connect to 3.10 with the following combination of options: - * Version V5 Flag UNSET + * Version V6 Flag UNSET * * @jira_ticket JAVA-1248 */ @@ -96,7 +96,7 @@ public void should_not_connect_when_beta_version_explicitly_required_and_flag_no Cluster.builder() .addContactPoints(getContactPoints()) .withPort(ccm().getBinaryPort()) - .withProtocolVersion(V5) + .withProtocolVersion(V6) .build(); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException e) { @@ -127,7 +127,7 @@ public void should_connect_with_beta_when_no_version_explicitly_required_and_fla .allowBetaProtocolVersion() .build(); cluster.connect(); - assertThat(cluster.getConfiguration().getProtocolOptions().getProtocolVersion()).isEqualTo(V5); + assertThat(cluster.getConfiguration().getProtocolOptions().getProtocolVersion()).isEqualTo(V6); } /** diff --git a/driver-core/src/test/java/com/datastax/driver/core/ProtocolVersionRenegotiationTest.java b/driver-core/src/test/java/com/datastax/driver/core/ProtocolVersionRenegotiationTest.java index 927fbb89cac..c740f0d3f45 100644 --- a/driver-core/src/test/java/com/datastax/driver/core/ProtocolVersionRenegotiationTest.java +++ b/driver-core/src/test/java/com/datastax/driver/core/ProtocolVersionRenegotiationTest.java @@ -17,7 +17,7 @@ import static com.datastax.driver.core.ProtocolVersion.V1; import static com.datastax.driver.core.ProtocolVersion.V4; -import static com.datastax.driver.core.ProtocolVersion.V5; +import static com.datastax.driver.core.ProtocolVersion.V6; import static org.assertj.core.api.Assertions.assertThat; import com.datastax.driver.core.exceptions.UnsupportedProtocolVersionException; @@ -67,11 +67,11 @@ public void should_fail_when_version_provided_and_too_high() throws Exception { /** @jira_ticket JAVA-1367 */ @Test(groups = "short") public void should_fail_when_beta_allowed_and_too_high() throws Exception { - if (ccm().getCassandraVersion().compareTo(VersionNumber.parse("3.10")) >= 0) { - throw new SkipException("Server supports protocol protocol V5 beta"); + if (ccm().getCassandraVersion().compareTo(VersionNumber.parse("4.0.0")) >= 0) { + throw new SkipException("Server supports protocol protocol V6 beta"); } UnsupportedProtocolVersionException e = connectWithUnsupportedBetaVersion(); - assertThat(e.getUnsupportedVersion()).isEqualTo(V5); + assertThat(e.getUnsupportedVersion()).isEqualTo(V6); } /** @jira_ticket JAVA-1367 */ diff --git a/driver-core/src/test/java/com/datastax/driver/core/SegmentBuilderTest.java b/driver-core/src/test/java/com/datastax/driver/core/SegmentBuilderTest.java index 40adfb66dc3..e9537424b92 100644 --- a/driver-core/src/test/java/com/datastax/driver/core/SegmentBuilderTest.java +++ b/driver-core/src/test/java/com/datastax/driver/core/SegmentBuilderTest.java @@ -35,7 +35,7 @@ public class SegmentBuilderTest { private static final Message.ProtocolEncoder REQUEST_ENCODER = - new Message.ProtocolEncoder(ProtocolVersion.V5); + new Message.ProtocolEncoder(ProtocolVersion.V6); // The constant names denote the total encoded size, including the frame header private static final Message.Request _38B_REQUEST = new Requests.Query("SELECT * FROM table"); diff --git a/driver-core/src/test/java/com/datastax/driver/core/SegmentToFrameDecoderTest.java b/driver-core/src/test/java/com/datastax/driver/core/SegmentToFrameDecoderTest.java index bef4a5beaef..7ad78724881 100644 --- a/driver-core/src/test/java/com/datastax/driver/core/SegmentToFrameDecoderTest.java +++ b/driver-core/src/test/java/com/datastax/driver/core/SegmentToFrameDecoderTest.java @@ -33,7 +33,7 @@ public class SegmentToFrameDecoderTest { private static final ByteBuf SMALL_BODY_1 = buffer(128); private static final Header SMALL_HEADER_1 = new Header( - ProtocolVersion.V5, + ProtocolVersion.V6, EnumSet.noneOf(Flag.class), 2, READY.opcode, @@ -42,7 +42,7 @@ public class SegmentToFrameDecoderTest { private static final ByteBuf SMALL_BODY_2 = buffer(1024); private static final Header SMALL_HEADER_2 = new Header( - ProtocolVersion.V5, + ProtocolVersion.V6, EnumSet.noneOf(Flag.class), 7, RESULT.opcode, @@ -51,7 +51,7 @@ public class SegmentToFrameDecoderTest { private static final ByteBuf LARGE_BODY = buffer(256 * 1024); private static final Header LARGE_HEADER = new Header( - ProtocolVersion.V5, + ProtocolVersion.V6, EnumSet.noneOf(Flag.class), 12, RESULT.opcode, From b28cc77f9ac63a36b600f6e784168938dc079ebc Mon Sep 17 00:00:00 2001 From: Sam Tunnicliffe Date: Wed, 10 Feb 2021 12:06:51 +0000 Subject: [PATCH 2/4] Initial review comments --- .../src/main/java/com/datastax/driver/core/Connection.java | 1 + .../src/main/java/com/datastax/driver/core/ProtocolVersion.java | 2 +- .../test/java/com/datastax/driver/core/SegmentBuilderTest.java | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/driver-core/src/main/java/com/datastax/driver/core/Connection.java b/driver-core/src/main/java/com/datastax/driver/core/Connection.java index d953130cc6b..b373fbec578 100644 --- a/driver-core/src/main/java/com/datastax/driver/core/Connection.java +++ b/driver-core/src/main/java/com/datastax/driver/core/Connection.java @@ -1773,6 +1773,7 @@ private Message.ProtocolEncoder messageEncoderFor(ProtocolVersion version) { * v5. */ void switchToV5Framing() { + assert factory.protocolVersion.compareTo(ProtocolVersion.V5) >= 0; // We want to do this on the event loop, to make sure it doesn't race with incoming requests assert channel.eventLoop().inEventLoop(); diff --git a/driver-core/src/main/java/com/datastax/driver/core/ProtocolVersion.java b/driver-core/src/main/java/com/datastax/driver/core/ProtocolVersion.java index 10c696f638b..fb0ad0bcff9 100644 --- a/driver-core/src/main/java/com/datastax/driver/core/ProtocolVersion.java +++ b/driver-core/src/main/java/com/datastax/driver/core/ProtocolVersion.java @@ -26,7 +26,7 @@ public enum ProtocolVersion { V2("2.0.0", 2, V1), V3("2.1.0", 3, V2), V4("2.2.0", 4, V3), - V5("3.10.0", 5, V4), + V5("4.0.0", 5, V4), V6("4.0.0", 6, V5); /** The most recent protocol version supported by the driver. */ diff --git a/driver-core/src/test/java/com/datastax/driver/core/SegmentBuilderTest.java b/driver-core/src/test/java/com/datastax/driver/core/SegmentBuilderTest.java index e9537424b92..40adfb66dc3 100644 --- a/driver-core/src/test/java/com/datastax/driver/core/SegmentBuilderTest.java +++ b/driver-core/src/test/java/com/datastax/driver/core/SegmentBuilderTest.java @@ -35,7 +35,7 @@ public class SegmentBuilderTest { private static final Message.ProtocolEncoder REQUEST_ENCODER = - new Message.ProtocolEncoder(ProtocolVersion.V6); + new Message.ProtocolEncoder(ProtocolVersion.V5); // The constant names denote the total encoded size, including the frame header private static final Message.Request _38B_REQUEST = new Requests.Query("SELECT * FROM table"); From c1c425dd74969417b9a703132a75449d2525edcc Mon Sep 17 00:00:00 2001 From: Sam Tunnicliffe Date: Fri, 12 Feb 2021 18:13:31 +0000 Subject: [PATCH 3/4] Revert version change in SegmentToFrameDecoderTest --- .../com/datastax/driver/core/SegmentToFrameDecoderTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/driver-core/src/test/java/com/datastax/driver/core/SegmentToFrameDecoderTest.java b/driver-core/src/test/java/com/datastax/driver/core/SegmentToFrameDecoderTest.java index 7ad78724881..bef4a5beaef 100644 --- a/driver-core/src/test/java/com/datastax/driver/core/SegmentToFrameDecoderTest.java +++ b/driver-core/src/test/java/com/datastax/driver/core/SegmentToFrameDecoderTest.java @@ -33,7 +33,7 @@ public class SegmentToFrameDecoderTest { private static final ByteBuf SMALL_BODY_1 = buffer(128); private static final Header SMALL_HEADER_1 = new Header( - ProtocolVersion.V6, + ProtocolVersion.V5, EnumSet.noneOf(Flag.class), 2, READY.opcode, @@ -42,7 +42,7 @@ public class SegmentToFrameDecoderTest { private static final ByteBuf SMALL_BODY_2 = buffer(1024); private static final Header SMALL_HEADER_2 = new Header( - ProtocolVersion.V6, + ProtocolVersion.V5, EnumSet.noneOf(Flag.class), 7, RESULT.opcode, @@ -51,7 +51,7 @@ public class SegmentToFrameDecoderTest { private static final ByteBuf LARGE_BODY = buffer(256 * 1024); private static final Header LARGE_HEADER = new Header( - ProtocolVersion.V6, + ProtocolVersion.V5, EnumSet.noneOf(Flag.class), 12, RESULT.opcode, From afbe75d45905864cf1a2e42b63991dabfcf94c4a Mon Sep 17 00:00:00 2001 From: Sam Tunnicliffe Date: Fri, 12 Feb 2021 18:16:29 +0000 Subject: [PATCH 4/4] Add changelog entry --- changelog/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changelog/README.md b/changelog/README.md index 1b64f4aa38b..246eb753936 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -5,6 +5,10 @@ 3.x versions get published. --> +### 3.11.0 (in progress) + +- [improvement] JAVA-2705: Remove protocol v5 beta status, add v6-beta. + ## 3.10.2 - [bug] JAVA-2860: Avoid NPE if channel initialization crashes.