Skip to content

Commit d4f416f

Browse files
authored
Merge pull request #303 from cconlon/sessionHashCode
JSSE: add hashCode() to WolfSSLImplementSSLSession
2 parents d83237e + 37d55fe commit d4f416f

2 files changed

Lines changed: 292 additions & 0 deletions

File tree

src/java/com/wolfssl/provider/jsse/WolfSSLImplementSSLSession.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@
3131
import java.security.cert.X509Certificate;
3232
import java.security.cert.CertificateException;
3333
import java.security.cert.CertificateEncodingException;
34+
import java.util.Arrays;
3435
import java.util.Date;
3536
import java.util.List;
3637
import java.util.ArrayList;
3738
import java.util.HashMap;
39+
import java.util.Objects;
3840
import java.util.logging.Level;
3941
import java.util.logging.Logger;
4042
import java.util.Collections;
@@ -1160,5 +1162,55 @@ protected synchronized void finalize() throws Throwable
11601162

11611163
super.finalize();
11621164
}
1165+
1166+
/**
1167+
* Return hash code for this SSLSession.
1168+
*
1169+
* Hash code is computed from session ID, host, port, and creation time
1170+
* to handle cases where session ID may be null or empty (such as with
1171+
* TLS 1.3 session tickets before resumption, or error conditions).
1172+
*
1173+
* @return hash code for this SSLSession
1174+
*/
1175+
@Override
1176+
public int hashCode() {
1177+
1178+
byte[] id = getId();
1179+
1180+
return Objects.hash(
1181+
id == null ? 0 : Arrays.hashCode(id), host, port, creation);
1182+
}
1183+
1184+
/**
1185+
* Compares this SSLSession with another object for equality.
1186+
*
1187+
* Two WolfSSLImplementSSLSession objects are considered equal if they
1188+
* have the same session ID, host, port, and creation time. This method
1189+
* maintains the general contract with hashCode() as required by
1190+
* Object.equals().
1191+
*
1192+
* @param obj the object to compare with
1193+
* @return true if the objects are equal, false otherwise
1194+
*/
1195+
@Override
1196+
public boolean equals(Object obj) {
1197+
1198+
WolfSSLImplementSSLSession other;
1199+
1200+
if (this == obj) {
1201+
return true;
1202+
}
1203+
1204+
if (obj == null || getClass() != obj.getClass()) {
1205+
return false;
1206+
}
1207+
1208+
other = (WolfSSLImplementSSLSession) obj;
1209+
1210+
return Objects.deepEquals(this.getId(), other.getId())
1211+
&& Objects.equals(this.host, other.host)
1212+
&& this.port == other.port
1213+
&& Objects.equals(this.creation, other.creation);
1214+
}
11631215
}
11641216

src/test/com/wolfssl/provider/jsse/test/WolfSSLSessionTest.java

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,246 @@ public void testSignatureSchemes()
606606
}
607607
}
608608

609+
/**
610+
* Test SSLSession.hashCode().
611+
*
612+
* Verifies that WolfSSLImplementSSLSession declares its own hashCode()
613+
* method (not just inherited from Object). Tests hashCode consistency
614+
* and that different sessions produce different hash codes.
615+
*/
616+
@Test
617+
public void testSessionHashCode()
618+
throws NoSuchAlgorithmException, KeyManagementException,
619+
KeyStoreException, CertificateException, IOException,
620+
NoSuchProviderException, UnrecoverableKeyException {
621+
622+
int ret;
623+
SSLSession session;
624+
625+
System.out.print("\tTesting hashCode()");
626+
627+
SSLContext ctx = tf.createSSLContext("TLS", engineProvider);
628+
SSLEngine client = ctx.createSSLEngine("localhost", 12345);
629+
SSLEngine server = ctx.createSSLEngine();
630+
631+
if (client == null || server == null) {
632+
error("\t\t... failed");
633+
fail("failed to create engine");
634+
return;
635+
}
636+
637+
server.setUseClientMode(false);
638+
server.setNeedClientAuth(false);
639+
client.setUseClientMode(true);
640+
641+
ret = tf.testConnection(server, client, null, null, "Test hashCode");
642+
if (ret != 0) {
643+
error("\t\t... failed");
644+
fail("failed to create connection");
645+
return;
646+
}
647+
648+
session = client.getSession();
649+
if (session == null) {
650+
error("\t\t... failed");
651+
fail("SSLEngine.getSession() returned null");
652+
return;
653+
}
654+
655+
/* Test that hashCode() method is declared in the session class
656+
* (not just inherited from Object). */
657+
try {
658+
session.getClass().getDeclaredMethod("hashCode", new Class<?>[0]);
659+
} catch (NoSuchMethodException e) {
660+
error("\t\t... failed");
661+
fail("SSLSession class does not declare hashCode() method");
662+
return;
663+
}
664+
665+
/* Test that hashCode() returns consistent value */
666+
int hash1 = session.hashCode();
667+
int hash2 = session.hashCode();
668+
if (hash1 != hash2) {
669+
error("\t\t... failed");
670+
fail("SSLSession.hashCode() not consistent: " +
671+
hash1 + " != " + hash2);
672+
return;
673+
}
674+
675+
/* Test that different session has different hashCode.
676+
* Create another connection */
677+
SSLContext ctx2 = tf.createSSLContext("TLS", engineProvider);
678+
SSLEngine client2 = ctx2.createSSLEngine("localhost", 54321);
679+
SSLEngine server2 = ctx2.createSSLEngine();
680+
681+
server2.setUseClientMode(false);
682+
server2.setNeedClientAuth(false);
683+
client2.setUseClientMode(true);
684+
685+
ret = tf.testConnection(server2, client2, null, null,
686+
"Test hashCode 2");
687+
if (ret != 0) {
688+
error("\t\t... failed");
689+
fail("failed to create second connection");
690+
return;
691+
}
692+
693+
SSLSession session2 = client2.getSession();
694+
if (session2 == null) {
695+
error("\t\t... failed");
696+
fail("Second SSLEngine.getSession() returned null");
697+
return;
698+
}
699+
700+
/* Test different sessions should have different hashCodes */
701+
int hash3 = session2.hashCode();
702+
if (hash1 == hash3) {
703+
/* Not a hard failure, just a warning since hashCode collisions
704+
* are technically allowed */
705+
System.out.println(" (warning: hash collision)");
706+
}
707+
708+
pass("\t\t... passed");
709+
}
710+
711+
@Test
712+
public void testSessionEquals()
713+
throws NoSuchAlgorithmException, KeyManagementException,
714+
KeyStoreException, CertificateException, IOException,
715+
NoSuchProviderException, UnrecoverableKeyException {
716+
717+
int ret;
718+
SSLSession session;
719+
720+
System.out.print("\tTesting equals()");
721+
722+
SSLContext ctx = tf.createSSLContext("TLS", engineProvider);
723+
SSLEngine client = ctx.createSSLEngine("localhost", 12345);
724+
SSLEngine server = ctx.createSSLEngine();
725+
726+
if (client == null || server == null) {
727+
error("\t\t... failed");
728+
fail("failed to create engine");
729+
return;
730+
}
731+
732+
server.setUseClientMode(false);
733+
server.setNeedClientAuth(false);
734+
client.setUseClientMode(true);
735+
736+
ret = tf.testConnection(server, client, null, null, "Test equals");
737+
if (ret != 0) {
738+
error("\t\t... failed");
739+
fail("failed to create connection");
740+
return;
741+
}
742+
743+
session = client.getSession();
744+
if (session == null) {
745+
error("\t\t... failed");
746+
fail("SSLEngine.getSession() returned null");
747+
return;
748+
}
749+
750+
/* Test that equals() method is declared in the session class
751+
* (not just inherited from Object). */
752+
try {
753+
session.getClass().getDeclaredMethod("equals",
754+
new Class<?>[] { Object.class });
755+
} catch (NoSuchMethodException e) {
756+
error("\t\t... failed");
757+
fail("SSLSession class does not declare equals() method");
758+
return;
759+
}
760+
761+
/* Test reflexivity: session.equals(session) should be true */
762+
if (!session.equals(session)) {
763+
error("\t\t... failed");
764+
fail("SSLSession.equals() reflexivity failed");
765+
return;
766+
}
767+
768+
/* Test null: session.equals(null) should be false */
769+
if (session.equals(null)) {
770+
error("\t\t... failed");
771+
fail("SSLSession.equals(null) should return false");
772+
return;
773+
}
774+
775+
/* Test different type: session.equals(Object) should return false
776+
* when passed an incompatible type. This is intentional to verify
777+
* the equals() implementation handles type mismatches correctly. */
778+
Object differentType = "not a session";
779+
if (session.equals(differentType)) {
780+
error("\t\t... failed");
781+
fail("SSLSession.equals(Object) should return false for " +
782+
"incompatible type");
783+
return;
784+
}
785+
786+
/* Test hashCode/equals contract: equal objects must have same hash */
787+
if (session.equals(session) &&
788+
session.hashCode() != session.hashCode()) {
789+
error("\t\t... failed");
790+
fail("Equal sessions have different hashCodes");
791+
return;
792+
}
793+
794+
pass("\t\t... passed");
795+
}
796+
797+
@Test
798+
public void testSessionHashCodeBeforeHandshake()
799+
throws NoSuchAlgorithmException, KeyManagementException,
800+
KeyStoreException, CertificateException, IOException,
801+
NoSuchProviderException, UnrecoverableKeyException {
802+
803+
System.out.print("\tTesting hashCode() before HS");
804+
805+
SSLContext ctx = tf.createSSLContext("TLS", engineProvider);
806+
SSLEngine engine = ctx.createSSLEngine("localhost", 12345);
807+
808+
if (engine == null) {
809+
error("\t... failed");
810+
fail("failed to create engine");
811+
return;
812+
}
813+
814+
engine.setUseClientMode(true);
815+
816+
/* Get session before handshake - may have null session ID */
817+
SSLSession session = engine.getSession();
818+
if (session == null) {
819+
/* Some implementations return null before handshake */
820+
pass("\t... passed (null session)");
821+
return;
822+
}
823+
824+
/* Test that hashCode() does not throw even if getId() returns
825+
* null or empty (session not yet established) */
826+
try {
827+
int hash = session.hashCode();
828+
/* hashCode should work without throwing */
829+
} catch (Exception e) {
830+
error("\t... failed");
831+
fail("hashCode() threw exception before handshake: " +
832+
e.getMessage());
833+
return;
834+
}
835+
836+
/* Test that equals() does not throw either */
837+
try {
838+
boolean eq = session.equals(session);
839+
} catch (Exception e) {
840+
error("\t... failed");
841+
fail("equals() threw exception before handshake: " +
842+
e.getMessage());
843+
return;
844+
}
845+
846+
pass("\t... passed");
847+
}
848+
609849
/**
610850
* Test SSLSocket.getSession() and calling methods on the
611851
* SSLSession retrieved. */

0 commit comments

Comments
 (0)