--- a/src/java.base/share/classes/sun/security/ssl/CipherSuite.java Mon Sep 17 15:22:46 2018 -0700
+++ b/src/java.base/share/classes/sun/security/ssl/CipherSuite.java Mon Sep 17 15:25:42 2018 -0700
@@ -70,6 +70,9 @@
TLS_AES_256_GCM_SHA384(
0x1302, true, "TLS_AES_256_GCM_SHA384",
ProtocolVersion.PROTOCOLS_OF_13, B_AES_256_GCM_IV, H_SHA384),
+ TLS_CHACHA20_POLY1305_SHA256(
+ 0x1303, true, "TLS_CHACHA20_POLY1305_SHA256",
+ ProtocolVersion.PROTOCOLS_OF_13, B_CC20_P1305, H_SHA256),
// Suite B compliant cipher suites, see RFC 6460.
//
@@ -87,11 +90,22 @@
ProtocolVersion.PROTOCOLS_OF_12,
K_ECDHE_ECDSA, B_AES_128_GCM, M_NULL, H_SHA256),
+ // Not suite B, but we want it to position the suite early in the list
+ // of 1.2 suites.
+ TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256(
+ 0xCCA9, true, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", "",
+ ProtocolVersion.PROTOCOLS_OF_12,
+ K_ECDHE_ECDSA, B_CC20_P1305, M_NULL, H_SHA256),
+
// AES_256(GCM)
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(
0xC030, true, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "",
ProtocolVersion.PROTOCOLS_OF_12,
K_ECDHE_RSA, B_AES_256_GCM, M_NULL, H_SHA384),
+ TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256(
+ 0xCCA8, true, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "",
+ ProtocolVersion.PROTOCOLS_OF_12,
+ K_ECDHE_RSA, B_CC20_P1305, M_NULL, H_SHA256),
TLS_RSA_WITH_AES_256_GCM_SHA384(
0x009D, true, "TLS_RSA_WITH_AES_256_GCM_SHA384", "",
ProtocolVersion.PROTOCOLS_OF_12,
@@ -108,6 +122,10 @@
0x009F, true, "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "",
ProtocolVersion.PROTOCOLS_OF_12,
K_DHE_RSA, B_AES_256_GCM, M_NULL, H_SHA384),
+ TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256(
+ 0xCCAA, true, "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "",
+ ProtocolVersion.PROTOCOLS_OF_12,
+ K_DHE_RSA, B_CC20_P1305, M_NULL, H_SHA256),
TLS_DHE_DSS_WITH_AES_256_GCM_SHA384(
0x00A3, true, "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", "",
ProtocolVersion.PROTOCOLS_OF_12,
@@ -480,8 +498,6 @@
// Definition of the CipherSuites that are not supported but the names
// are known.
- TLS_CHACHA20_POLY1305_SHA256( // TLS 1.3
- "TLS_CHACHA20_POLY1305_SHA256", 0x1303),
TLS_AES_128_CCM_SHA256( // TLS 1.3
"TLS_AES_128_CCM_SHA256", 0x1304),
TLS_AES_128_CCM_8_SHA256( // TLS 1.3
--- a/src/java.base/share/classes/sun/security/ssl/SSLCipher.java Mon Sep 17 15:22:46 2018 -0700
+++ b/src/java.base/share/classes/sun/security/ssl/SSLCipher.java Mon Sep 17 15:25:42 2018 -0700
@@ -329,6 +329,32 @@
new T13GcmWriteCipherGenerator(),
ProtocolVersion.PROTOCOLS_OF_13
)
+ })),
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ B_CC20_P1305(CIPHER_CHACHA20_POLY1305, AEAD_CIPHER, 32, 32, 12,
+ 12, true, false,
+ (Map.Entry<ReadCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ new T12CC20P1305ReadCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_OF_12
+ ),
+ new SimpleImmutableEntry<ReadCipherGenerator, ProtocolVersion[]>(
+ new T13CC20P1305ReadCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_OF_13
+ )
+ }),
+ (Map.Entry<WriteCipherGenerator,
+ ProtocolVersion[]>[])(new Map.Entry[] {
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ new T12CC20P1305WriteCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_OF_12
+ ),
+ new SimpleImmutableEntry<WriteCipherGenerator, ProtocolVersion[]>(
+ new T13CC20P1305WriteCipherGenerator(),
+ ProtocolVersion.PROTOCOLS_OF_13
+ )
}));
// descriptive name including key size, e.g. AES/128
@@ -2082,6 +2108,549 @@
}
}
+ private static final class T12CC20P1305ReadCipherGenerator
+ implements ReadCipherGenerator {
+
+ @Override
+ public SSLReadCipher createCipher(SSLCipher sslCipher,
+ Authenticator authenticator, ProtocolVersion protocolVersion,
+ String algorithm, Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ return new CC20P1305ReadCipher(authenticator, protocolVersion,
+ sslCipher, algorithm, key, params, random);
+ }
+
+ static final class CC20P1305ReadCipher extends SSLReadCipher {
+ private final Cipher cipher;
+ private final int tagSize;
+ private final Key key;
+ private final byte[] iv;
+ private final SecureRandom random;
+
+ CC20P1305ReadCipher(Authenticator authenticator,
+ ProtocolVersion protocolVersion,
+ SSLCipher sslCipher, String algorithm,
+ Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ super(authenticator, protocolVersion);
+ this.cipher = JsseJce.getCipher(algorithm);
+ this.tagSize = sslCipher.tagSize;
+ this.key = key;
+ this.iv = ((IvParameterSpec)params).getIV();
+ this.random = random;
+
+ // DON'T initialize the cipher for AEAD!
+ }
+
+ @Override
+ public Plaintext decrypt(byte contentType, ByteBuffer bb,
+ byte[] sequence) throws GeneralSecurityException {
+ if (bb.remaining() <= tagSize) {
+ throw new BadPaddingException(
+ "Insufficient buffer remaining for AEAD cipher " +
+ "fragment (" + bb.remaining() + "). Needs to be " +
+ "more than tag size (" + tagSize + ")");
+ }
+
+ byte[] sn = sequence;
+ if (sn == null) {
+ sn = authenticator.sequenceNumber();
+ }
+ byte[] nonce = new byte[iv.length];
+ System.arraycopy(sn, 0, nonce, nonce.length - sn.length,
+ sn.length);
+ for (int i = 0; i < nonce.length; i++) {
+ nonce[i] ^= iv[i];
+ }
+
+ // initialize the AEAD cipher with the unique IV
+ AlgorithmParameterSpec spec = new IvParameterSpec(nonce);
+ try {
+ cipher.init(Cipher.DECRYPT_MODE, key, spec, random);
+ } catch (InvalidKeyException |
+ InvalidAlgorithmParameterException ikae) {
+ // unlikely to happen
+ throw new RuntimeException(
+ "invalid key or spec in AEAD mode", ikae);
+ }
+
+ // update the additional authentication data
+ byte[] aad = authenticator.acquireAuthenticationBytes(
+ contentType, bb.remaining() - tagSize, sequence);
+ cipher.updateAAD(aad);
+
+ // DON'T decrypt the nonce_explicit for AEAD mode. The buffer
+ // position has moved out of the nonce_explicit range.
+ int len = bb.remaining();
+ int pos = bb.position();
+ ByteBuffer dup = bb.duplicate();
+ try {
+ len = cipher.doFinal(dup, bb);
+ } catch (IllegalBlockSizeException ibse) {
+ // unlikely to happen
+ throw new RuntimeException(
+ "Cipher error in AEAD mode \"" + ibse.getMessage() +
+ " \"in JCE provider " + cipher.getProvider().getName());
+ } catch (ShortBufferException sbe) {
+ // catch BouncyCastle buffering error
+ throw new RuntimeException("Cipher buffering error in " +
+ "JCE provider " + cipher.getProvider().getName(), sbe);
+ }
+ // reset the limit to the end of the decrypted data
+ bb.position(pos);
+ bb.limit(pos + len);
+
+ if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) {
+ SSLLogger.fine(
+ "Plaintext after DECRYPTION", bb.duplicate());
+ }
+
+ return new Plaintext(contentType,
+ ProtocolVersion.NONE.major, ProtocolVersion.NONE.minor,
+ -1, -1L, bb.slice());
+ }
+
+ @Override
+ void dispose() {
+ if (cipher != null) {
+ try {
+ cipher.doFinal();
+ } catch (Exception e) {
+ // swallow all types of exceptions.
+ }
+ }
+ }
+
+ @Override
+ int estimateFragmentSize(int packetSize, int headerSize) {
+ return packetSize - headerSize - tagSize;
+ }
+ }
+ }
+
+ private static final class T12CC20P1305WriteCipherGenerator
+ implements WriteCipherGenerator {
+ @Override
+ public SSLWriteCipher createCipher(SSLCipher sslCipher,
+ Authenticator authenticator, ProtocolVersion protocolVersion,
+ String algorithm, Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ return new CC20P1305WriteCipher(authenticator, protocolVersion,
+ sslCipher, algorithm, key, params, random);
+ }
+
+ private static final class CC20P1305WriteCipher extends SSLWriteCipher {
+ private final Cipher cipher;
+ private final int tagSize;
+ private final Key key;
+ private final byte[] iv;
+ private final SecureRandom random;
+
+ CC20P1305WriteCipher(Authenticator authenticator,
+ ProtocolVersion protocolVersion,
+ SSLCipher sslCipher, String algorithm,
+ Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ super(authenticator, protocolVersion);
+ this.cipher = JsseJce.getCipher(algorithm);
+ this.tagSize = sslCipher.tagSize;
+ this.key = key;
+ this.iv = ((IvParameterSpec)params).getIV();
+ this.random = random;
+
+ keyLimitCountdown = cipherLimits.getOrDefault(
+ algorithm.toUpperCase() + ":" + tag[0], 0L);
+ if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+ SSLLogger.fine("algorithm = " + algorithm.toUpperCase() +
+ ":" + tag[0] + "\ncountdown value = " +
+ keyLimitCountdown);
+ }
+ if (keyLimitCountdown > 0) {
+ keyLimitEnabled = true;
+ }
+
+ // DON'T initialize the cipher for AEAD!
+ }
+
+ @Override
+ public int encrypt(byte contentType,
+ ByteBuffer bb) {
+ byte[] sn = authenticator.sequenceNumber();
+ byte[] nonce = new byte[iv.length];
+ System.arraycopy(sn, 0, nonce, nonce.length - sn.length,
+ sn.length);
+ for (int i = 0; i < nonce.length; i++) {
+ nonce[i] ^= iv[i];
+ }
+
+ // initialize the AEAD cipher for the unique IV
+ AlgorithmParameterSpec spec = new IvParameterSpec(nonce);
+ try {
+ cipher.init(Cipher.ENCRYPT_MODE, key, spec, random);
+ } catch (InvalidKeyException |
+ InvalidAlgorithmParameterException ikae) {
+ // unlikely to happen
+ throw new RuntimeException(
+ "invalid key or spec in AEAD mode", ikae);
+ }
+
+ // Update the additional authentication data, using the
+ // implicit sequence number of the authenticator.
+ byte[] aad = authenticator.acquireAuthenticationBytes(
+ contentType, bb.remaining(), null);
+ cipher.updateAAD(aad);
+
+ // DON'T encrypt the nonce for AEAD mode.
+ int len = bb.remaining();
+ int pos = bb.position();
+ if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) {
+ SSLLogger.fine(
+ "Plaintext before ENCRYPTION",
+ bb.duplicate());
+ }
+
+ ByteBuffer dup = bb.duplicate();
+ int outputSize = cipher.getOutputSize(dup.remaining());
+ if (outputSize > bb.remaining()) {
+ // Need to expand the limit of the output buffer for
+ // the authentication tag.
+ //
+ // DON'T worry about the buffer's capacity, we have
+ // reserved space for the authentication tag.
+ bb.limit(pos + outputSize);
+ }
+
+ try {
+ len = cipher.doFinal(dup, bb);
+ } catch (IllegalBlockSizeException |
+ BadPaddingException | ShortBufferException ibse) {
+ // unlikely to happen
+ throw new RuntimeException(
+ "Cipher error in AEAD mode in JCE provider " +
+ cipher.getProvider().getName(), ibse);
+ }
+
+ if (len != outputSize) {
+ throw new RuntimeException(
+ "Cipher buffering error in JCE provider " +
+ cipher.getProvider().getName());
+ }
+
+ return len;
+ }
+
+ @Override
+ void dispose() {
+ if (cipher != null) {
+ try {
+ cipher.doFinal();
+ } catch (Exception e) {
+ // swallow all types of exceptions.
+ }
+ }
+ }
+
+ @Override
+ int getExplicitNonceSize() {
+ return 0;
+ }
+
+ @Override
+ int calculateFragmentSize(int packetLimit, int headerSize) {
+ return packetLimit - headerSize - tagSize;
+ }
+
+ @Override
+ int calculatePacketSize(int fragmentSize, int headerSize) {
+ return fragmentSize + headerSize + tagSize;
+ }
+ }
+ }
+
+ private static final class T13CC20P1305ReadCipherGenerator
+ implements ReadCipherGenerator {
+
+ @Override
+ public SSLReadCipher createCipher(SSLCipher sslCipher,
+ Authenticator authenticator, ProtocolVersion protocolVersion,
+ String algorithm, Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ return new CC20P1305ReadCipher(authenticator, protocolVersion,
+ sslCipher, algorithm, key, params, random);
+ }
+
+ static final class CC20P1305ReadCipher extends SSLReadCipher {
+ private final Cipher cipher;
+ private final int tagSize;
+ private final Key key;
+ private final byte[] iv;
+ private final SecureRandom random;
+
+ CC20P1305ReadCipher(Authenticator authenticator,
+ ProtocolVersion protocolVersion,
+ SSLCipher sslCipher, String algorithm,
+ Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ super(authenticator, protocolVersion);
+ this.cipher = JsseJce.getCipher(algorithm);
+ this.tagSize = sslCipher.tagSize;
+ this.key = key;
+ this.iv = ((IvParameterSpec)params).getIV();
+ this.random = random;
+
+ // DON'T initialize the cipher for AEAD!
+ }
+
+ @Override
+ public Plaintext decrypt(byte contentType, ByteBuffer bb,
+ byte[] sequence) throws GeneralSecurityException {
+ // An implementation may receive an unencrypted record of type
+ // change_cipher_spec consisting of the single byte value 0x01
+ // at any time after the first ClientHello message has been
+ // sent or received and before the peer's Finished message has
+ // been received and MUST simply drop it without further
+ // processing.
+ if (contentType == ContentType.CHANGE_CIPHER_SPEC.id) {
+ return new Plaintext(contentType,
+ ProtocolVersion.NONE.major, ProtocolVersion.NONE.minor,
+ -1, -1L, bb.slice());
+ }
+
+ if (bb.remaining() <= tagSize) {
+ throw new BadPaddingException(
+ "Insufficient buffer remaining for AEAD cipher " +
+ "fragment (" + bb.remaining() + "). Needs to be " +
+ "more than tag size (" + tagSize + ")");
+ }
+
+ byte[] sn = sequence;
+ if (sn == null) {
+ sn = authenticator.sequenceNumber();
+ }
+ byte[] nonce = new byte[iv.length];
+ System.arraycopy(sn, 0, nonce, nonce.length - sn.length,
+ sn.length);
+ for (int i = 0; i < nonce.length; i++) {
+ nonce[i] ^= iv[i];
+ }
+
+ // initialize the AEAD cipher with the unique IV
+ AlgorithmParameterSpec spec = new IvParameterSpec(nonce);
+ try {
+ cipher.init(Cipher.DECRYPT_MODE, key, spec, random);
+ } catch (InvalidKeyException |
+ InvalidAlgorithmParameterException ikae) {
+ // unlikely to happen
+ throw new RuntimeException(
+ "invalid key or spec in AEAD mode", ikae);
+ }
+
+ // Update the additional authentication data, using the
+ // implicit sequence number of the authenticator.
+ byte[] aad = authenticator.acquireAuthenticationBytes(
+ contentType, bb.remaining(), sn);
+ cipher.updateAAD(aad);
+
+ int len = bb.remaining();
+ int pos = bb.position();
+ ByteBuffer dup = bb.duplicate();
+ try {
+ len = cipher.doFinal(dup, bb);
+ } catch (IllegalBlockSizeException ibse) {
+ // unlikely to happen
+ throw new RuntimeException(
+ "Cipher error in AEAD mode \"" + ibse.getMessage() +
+ " \"in JCE provider " + cipher.getProvider().getName());
+ } catch (ShortBufferException sbe) {
+ // catch BouncyCastle buffering error
+ throw new RuntimeException("Cipher buffering error in " +
+ "JCE provider " + cipher.getProvider().getName(), sbe);
+ }
+ // reset the limit to the end of the decrypted data
+ bb.position(pos);
+ bb.limit(pos + len);
+
+ // remove inner plaintext padding
+ int i = bb.limit() - 1;
+ for (; i > 0 && bb.get(i) == 0; i--) {
+ // blank
+ }
+ if (i < (pos + 1)) {
+ throw new BadPaddingException(
+ "Incorrect inner plaintext: no content type");
+ }
+ contentType = bb.get(i);
+ bb.limit(i);
+
+ if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) {
+ SSLLogger.fine(
+ "Plaintext after DECRYPTION", bb.duplicate());
+ }
+
+ return new Plaintext(contentType,
+ ProtocolVersion.NONE.major, ProtocolVersion.NONE.minor,
+ -1, -1L, bb.slice());
+ }
+
+ @Override
+ void dispose() {
+ if (cipher != null) {
+ try {
+ cipher.doFinal();
+ } catch (Exception e) {
+ // swallow all types of exceptions.
+ }
+ }
+ }
+
+ @Override
+ int estimateFragmentSize(int packetSize, int headerSize) {
+ return packetSize - headerSize - tagSize;
+ }
+ }
+ }
+
+ private static final class T13CC20P1305WriteCipherGenerator
+ implements WriteCipherGenerator {
+ @Override
+ public SSLWriteCipher createCipher(SSLCipher sslCipher,
+ Authenticator authenticator, ProtocolVersion protocolVersion,
+ String algorithm, Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ return new CC20P1305WriteCipher(authenticator, protocolVersion,
+ sslCipher, algorithm, key, params, random);
+ }
+
+ private static final class CC20P1305WriteCipher extends SSLWriteCipher {
+ private final Cipher cipher;
+ private final int tagSize;
+ private final Key key;
+ private final byte[] iv;
+ private final SecureRandom random;
+
+ CC20P1305WriteCipher(Authenticator authenticator,
+ ProtocolVersion protocolVersion,
+ SSLCipher sslCipher, String algorithm,
+ Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws GeneralSecurityException {
+ super(authenticator, protocolVersion);
+ this.cipher = JsseJce.getCipher(algorithm);
+ this.tagSize = sslCipher.tagSize;
+ this.key = key;
+ this.iv = ((IvParameterSpec)params).getIV();
+ this.random = random;
+
+ keyLimitCountdown = cipherLimits.getOrDefault(
+ algorithm.toUpperCase() + ":" + tag[0], 0L);
+ if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+ SSLLogger.fine("algorithm = " + algorithm.toUpperCase() +
+ ":" + tag[0] + "\ncountdown value = " +
+ keyLimitCountdown);
+ }
+ if (keyLimitCountdown > 0) {
+ keyLimitEnabled = true;
+ }
+
+ // DON'T initialize the cipher for AEAD!
+ }
+
+ @Override
+ public int encrypt(byte contentType,
+ ByteBuffer bb) {
+ byte[] sn = authenticator.sequenceNumber();
+ byte[] nonce = new byte[iv.length];
+ System.arraycopy(sn, 0, nonce, nonce.length - sn.length,
+ sn.length);
+ for (int i = 0; i < nonce.length; i++) {
+ nonce[i] ^= iv[i];
+ }
+
+ // initialize the AEAD cipher for the unique IV
+ AlgorithmParameterSpec spec = new IvParameterSpec(nonce);
+ try {
+ cipher.init(Cipher.ENCRYPT_MODE, key, spec, random);
+ } catch (InvalidKeyException |
+ InvalidAlgorithmParameterException ikae) {
+ // unlikely to happen
+ throw new RuntimeException(
+ "invalid key or spec in AEAD mode", ikae);
+ }
+
+ // Update the additional authentication data, using the
+ // implicit sequence number of the authenticator.
+ int outputSize = cipher.getOutputSize(bb.remaining());
+ byte[] aad = authenticator.acquireAuthenticationBytes(
+ contentType, outputSize, sn);
+ cipher.updateAAD(aad);
+
+ int len = bb.remaining();
+ int pos = bb.position();
+ if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) {
+ SSLLogger.fine(
+ "Plaintext before ENCRYPTION",
+ bb.duplicate());
+ }
+
+ ByteBuffer dup = bb.duplicate();
+ if (outputSize > bb.remaining()) {
+ // Need to expand the limit of the output buffer for
+ // the authentication tag.
+ //
+ // DON'T worry about the buffer's capacity, we have
+ // reserved space for the authentication tag.
+ bb.limit(pos + outputSize);
+ }
+
+ try {
+ len = cipher.doFinal(dup, bb);
+ } catch (IllegalBlockSizeException |
+ BadPaddingException | ShortBufferException ibse) {
+ // unlikely to happen
+ throw new RuntimeException(
+ "Cipher error in AEAD mode in JCE provider " +
+ cipher.getProvider().getName(), ibse);
+ }
+
+ if (len != outputSize) {
+ throw new RuntimeException(
+ "Cipher buffering error in JCE provider " +
+ cipher.getProvider().getName());
+ }
+
+ if (keyLimitEnabled) {
+ keyLimitCountdown -= len;
+ }
+ return len;
+ }
+
+ @Override
+ void dispose() {
+ if (cipher != null) {
+ try {
+ cipher.doFinal();
+ } catch (Exception e) {
+ // swallow all types of exceptions.
+ }
+ }
+ }
+
+ @Override
+ int getExplicitNonceSize() {
+ return 0;
+ }
+
+ @Override
+ int calculateFragmentSize(int packetLimit, int headerSize) {
+ return packetLimit - headerSize - tagSize;
+ }
+
+ @Override
+ int calculatePacketSize(int fragmentSize, int headerSize) {
+ return fragmentSize + headerSize + tagSize;
+ }
+ }
+ }
+
private static void addMac(MAC signer,
ByteBuffer destination, byte contentType) {
if (signer.macAlg().size != 0) {
@@ -2367,4 +2936,3 @@
return results;
}
}
-