@@ -313,6 +313,79 @@ public String toString() {
313313 **/
314314 private static final Lock sLock = new ReentrantLock ();
315315
316+ static final String USER_AGENT_TEMPLATE = "%s|%s|%s|%s|%s|%s|%s" ;
317+ static final String USER_AGENT_EXT_VERSION_AND_DRIVER_NAME = "1|MS-JDBC" ;
318+ static final String userAgentStr ;
319+
320+ static {
321+ userAgentStr = getUserAgent ();
322+ }
323+
324+ static String getUserAgent () {
325+ try {
326+ return String .format (
327+ USER_AGENT_TEMPLATE ,
328+ "1" ,
329+ "MS-JDBC" ,
330+ getJDBCVersion (),
331+ getOSType (),
332+ getOSDetails (),
333+ getArchitecture (),
334+ getRuntimeDetails ()
335+ );
336+ } catch (Exception e ) {
337+ return USER_AGENT_EXT_VERSION_AND_DRIVER_NAME ;
338+ }
339+ }
340+
341+ static String getJDBCVersion () {
342+ return sanitizeField (SQLJdbcVersion .MAJOR + "." + SQLJdbcVersion .MINOR + "." + SQLJdbcVersion .PATCH + "." + SQLJdbcVersion .BUILD + SQLJdbcVersion .RELEASE_EXT , 24 );
343+ }
344+
345+ static String getOSType () {
346+ String osName = System .getProperty ("os.name" , "Unknown" ).trim ().toLowerCase ();
347+ String osNameToReturn = "Unknown" ;
348+ if (osName .startsWith ("windows" )) {
349+ osNameToReturn = "Windows" ;
350+ } else if (osName .startsWith ("linux" )) {
351+ osNameToReturn = "Linux" ;
352+ } else if (osName .startsWith ("mac" )) {
353+ osNameToReturn = "macOS" ;
354+ } else if (osName .startsWith ("freebsd" )) {
355+ osNameToReturn = "FreeBSD" ;
356+ } else if (osName .startsWith ("android" )) {
357+ osNameToReturn = "Android" ;
358+ }
359+ return sanitizeField (osNameToReturn , 10 );
360+ }
361+
362+ static String getArchitecture () {
363+ return sanitizeField (System .getProperty ("os.arch" , "Unknown" ).trim (), 10 );
364+ }
365+
366+ static String getOSDetails () {
367+ String osName = System .getProperty ("os.name" , "" ).trim ();
368+ String osVersion = System .getProperty ("os.version" , "" ).trim ();
369+ if (osName .isEmpty () && osVersion .isEmpty ()) {
370+ return "Unknown" ;
371+ }
372+ return sanitizeField (osName + " " + osVersion , 44 );
373+ }
374+
375+ static String getRuntimeDetails () {
376+ String javaVmName = System .getProperty ("java.vm.name" , "" ).trim ();
377+ String javaVmVersion = System .getProperty ("java.vm.version" , "" ).trim ();
378+ if (javaVmName .isEmpty () && javaVmVersion .isEmpty ()) {
379+ return "Unknown" ;
380+ }
381+ return sanitizeField (javaVmName + " " + javaVmVersion , 44 );
382+ }
383+
384+ static String sanitizeField (String field , int maxLength ) {
385+ String sanitized = field .replaceAll ("[^A-Za-z0-9 .+_-]" , "" ).trim ();
386+ return (sanitized == null || sanitized .isEmpty ()) ? "Unknown" : sanitized .substring (0 , Math .min (sanitized .length (), maxLength ));
387+ }
388+
316389 /**
317390 * Generate a 6 byte random array for netAddress
318391 * As per TDS spec this is a unique clientID (MAC address) used to identify the client.
@@ -5789,6 +5862,27 @@ int writeDNSCacheFeatureRequest(boolean write, /* if false just calculates the l
57895862 return len ;
57905863 }
57915864
5865+ /**
5866+ * Writes the user agent telemetry feature request
5867+ * @param write
5868+ * If true, writes the feature request to the physical state object.
5869+ * @param tdsWriter
5870+ * @return
5871+ * The length of the feature request in bytes, or 0 if vectorTypeSupport is "off".
5872+ * @throws SQLServerException
5873+ */
5874+ int writeUserAgentFeatureRequest (boolean write , /* if false just calculates the length */
5875+ TDSWriter tdsWriter ) throws SQLServerException {
5876+ byte [] userAgentToSendBytes = toUCS16 (userAgentStr );
5877+ int len = userAgentToSendBytes .length + 5 ; // 1byte = featureID, 1byte = version, 4byte = feature data length in bytes, remaining bytes: feature data
5878+ if (write ) {
5879+ tdsWriter .writeByte (TDS .TDS_FEATURE_EXT_USERAGENT );
5880+ tdsWriter .writeInt (userAgentToSendBytes .length );
5881+ tdsWriter .writeBytes (userAgentToSendBytes );
5882+ }
5883+ return len ;
5884+ }
5885+
57925886 /**
57935887 * Writes the Vector Support feature request to the physical state object,
57945888 * unless vectorTypeSupport is "off". The request includes the feature ID,
@@ -7043,6 +7137,14 @@ private void onFeatureExtAck(byte featureId, byte[] data) throws SQLServerExcept
70437137 break ;
70447138 }
70457139
7140+ case TDS .TDS_FEATURE_EXT_USERAGENT : {
7141+ if (connectionlogger .isLoggable (Level .FINER )) {
7142+ connectionlogger .fine (
7143+ toString () + " Received feature extension acknowledgement for User agent feature extension. Received byte: " + data [0 ]);
7144+ }
7145+ break ;
7146+ }
7147+
70467148 default : {
70477149 // Unknown feature ack
70487150 throw new SQLServerException (SQLServerException .getErrString ("R_UnknownFeatureAck" ), null );
@@ -7330,6 +7432,9 @@ final boolean complete(LogonCommand logonCommand, TDSReader tdsReader) throws SQ
73307432 }
73317433
73327434 int aeOffset = len ;
7435+
7436+ len += writeUserAgentFeatureRequest (false , tdsWriter );
7437+
73337438 // AE is always ON
73347439 len += writeAEFeatureRequest (false , tdsWriter );
73357440 if (federatedAuthenticationInfoRequested || federatedAuthenticationRequested ) {
@@ -7534,6 +7639,9 @@ final boolean complete(LogonCommand logonCommand, TDSReader tdsReader) throws SQ
75347639 tdsWriter .writeBytes (secBlob , 0 , secBlob .length );
75357640 }
75367641
7642+ //Write user agent string
7643+ writeUserAgentFeatureRequest (true , tdsWriter );
7644+
75377645 // AE is always ON
75387646 writeAEFeatureRequest (true , tdsWriter );
75397647
0 commit comments