Skip to content

Commit f55f130

Browse files
authored
Send user agent info as a new feature extension to server (#2793)
* POC change to send user agent via feature ext. * Change FE identifier and encoding to UCS16 * Removed ApplicationAttributes logic * Changed sanitizing logic to not replace non-ascii chars * Send user agent FE as the first FE * Removed trailing $1 from the string template * Removed commented code * Removed unused constant
1 parent 7f4a3a3 commit f55f130

File tree

2 files changed

+107
-1
lines changed

2 files changed

+107
-1
lines changed

src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ final class TDS {
178178
static final byte TDS_FEATURE_EXT_JSONSUPPORT = 0x0D;
179179
static final byte JSONSUPPORT_NOT_SUPPORTED = 0x00;
180180
static final byte MAX_JSONSUPPORT_VERSION = 0x01;
181+
// User agent telemetry support
182+
static final byte TDS_FEATURE_EXT_USERAGENT = 0x10;
183+
static final byte MAX_USERAGENT_VERSION = 0x01;
181184

182185
static final int TDS_TVP = 0xF3;
183186
static final int TVP_ROW = 0x01;
@@ -251,7 +254,9 @@ static final String getTokenName(int tdsTokenType) {
251254
return "TDS_FEATURE_EXT_VECTORSUPPORT (0x0E)";
252255
case TDS_FEATURE_EXT_JSONSUPPORT:
253256
return "TDS_FEATURE_EXT_JSONSUPPORT (0x0D)";
254-
257+
case TDS_FEATURE_EXT_USERAGENT:
258+
return "TDS_FEATURE_EXT_USERAGENT (0x10)";
259+
255260
default:
256261
return "unknown token (0x" + Integer.toHexString(tdsTokenType).toUpperCase() + ")";
257262
}

src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,72 @@ public String toString() {
313313
**/
314314
private static final Lock sLock = new ReentrantLock();
315315

316+
static final String USER_AGENT_TEMPLATE = "{\"driver\":\"%s\",\"version\":\"%s\",\"os\":{\"type\":\"%s\",\"details\":\"%s\"},\"arch\":\"%s\",\"runtime\":\"%s\"}";
317+
static final String userAgentStr;
318+
319+
static {
320+
userAgentStr = getUserAgent();
321+
}
322+
323+
static String getUserAgent() {
324+
try {
325+
return String.format(
326+
USER_AGENT_TEMPLATE,
327+
"MS-JDBC",
328+
getJDBCVersion(),
329+
getOSType(),
330+
getOSDetails(),
331+
getArchitecture(),
332+
getRuntimeDetails()
333+
);
334+
} catch(Exception e) {
335+
return "{\"driver\":\"MS-JDBC\"}";
336+
}
337+
}
338+
339+
static String getJDBCVersion() {
340+
return sanitizeField(SQLJdbcVersion.MAJOR + "." + SQLJdbcVersion.MINOR + "." + SQLJdbcVersion.PATCH + "." + SQLJdbcVersion.BUILD + SQLJdbcVersion.RELEASE_EXT, 16);
341+
}
342+
343+
static String getOSType() {
344+
String osName = System.getProperty("os.name", "Unknown").trim();
345+
String osNameToReturn = "Unknown";
346+
if (osName.startsWith("Windows")) {
347+
osNameToReturn = "Windows";
348+
} else if (osName.startsWith("Linux")) {
349+
osNameToReturn = "Linux";
350+
} else if (osName.startsWith("Mac")) {
351+
osNameToReturn = "macOS";
352+
} else if (osName.startsWith("FreeBSD")) {
353+
osNameToReturn = "FreeBSD";
354+
} else if (osName.startsWith("Android")) {
355+
osNameToReturn = "Android";
356+
}
357+
return sanitizeField(osNameToReturn, 16);
358+
}
359+
360+
static String getArchitecture() {
361+
return sanitizeField(System.getProperty("os.arch", "Unknown").trim(), 16);
362+
}
363+
364+
static String getOSDetails() {
365+
String osName = System.getProperty("os.name", "").trim();
366+
String osVersion = System.getProperty("os.version", "").trim();
367+
if (osName.isEmpty() && osVersion.isEmpty()) return "Unknown";
368+
return sanitizeField(osName + " " + osVersion, 128);
369+
}
370+
371+
static String getRuntimeDetails() {
372+
String javaVmName = System.getProperty("java.vm.name", "").trim();
373+
String javaVmVersion = System.getProperty("java.vm.version", "").trim();
374+
if (javaVmName.isEmpty() && javaVmVersion.isEmpty()) return "Unknown";
375+
return sanitizeField(javaVmName + " " + javaVmVersion, 128);
376+
}
377+
378+
static String sanitizeField(String field, int maxLength) {
379+
return (field == null || field.isEmpty()) ? "Unknown" : field.substring(0, Math.min(field.length(), maxLength));
380+
}
381+
316382
/**
317383
* Generate a 6 byte random array for netAddress
318384
* As per TDS spec this is a unique clientID (MAC address) used to identify the client.
@@ -5685,6 +5751,28 @@ int writeDNSCacheFeatureRequest(boolean write, /* if false just calculates the l
56855751
return len;
56865752
}
56875753

5754+
/**
5755+
* Writes the user agent telemetry feature request
5756+
* @param write
5757+
* If true, writes the feature request to the physical state object.
5758+
* @param tdsWriter
5759+
* @return
5760+
* The length of the feature request in bytes, or 0 if vectorTypeSupport is "off".
5761+
* @throws SQLServerException
5762+
*/
5763+
int writeUserAgentFeatureRequest(boolean write, /* if false just calculates the length */
5764+
TDSWriter tdsWriter) throws SQLServerException {
5765+
byte[] userAgentToSendBytes = toUCS16(userAgentStr);
5766+
int len = userAgentToSendBytes.length + 6; // 1byte = featureID, 1byte = version, 4byte = feature data length in bytes, remaining bytes: feature data
5767+
if (write) {
5768+
tdsWriter.writeByte(TDS.TDS_FEATURE_EXT_USERAGENT);
5769+
tdsWriter.writeInt(userAgentToSendBytes.length + 1);
5770+
tdsWriter.writeByte(TDS.MAX_USERAGENT_VERSION);
5771+
tdsWriter.writeBytes(userAgentToSendBytes);
5772+
}
5773+
return len;
5774+
}
5775+
56885776
/**
56895777
* Writes the Vector Support feature request to the physical state object,
56905778
* unless vectorTypeSupport is "off". The request includes the feature ID,
@@ -6903,6 +6991,14 @@ private void onFeatureExtAck(byte featureId, byte[] data) throws SQLServerExcept
69036991
break;
69046992
}
69056993

6994+
case TDS.TDS_FEATURE_EXT_USERAGENT: {
6995+
if (connectionlogger.isLoggable(Level.FINER)) {
6996+
connectionlogger.fine(
6997+
toString() + " Received feature extension acknowledgement for User agent feature extension. Received byte: " + data[0]);
6998+
}
6999+
break;
7000+
}
7001+
69067002
default: {
69077003
// Unknown feature ack
69087004
throw new SQLServerException(SQLServerException.getErrString("R_UnknownFeatureAck"), null);
@@ -7190,6 +7286,9 @@ final boolean complete(LogonCommand logonCommand, TDSReader tdsReader) throws SQ
71907286
}
71917287

71927288
int aeOffset = len;
7289+
7290+
len += writeUserAgentFeatureRequest(false, tdsWriter);
7291+
71937292
// AE is always ON
71947293
len += writeAEFeatureRequest(false, tdsWriter);
71957294
if (federatedAuthenticationInfoRequested || federatedAuthenticationRequested) {
@@ -7394,6 +7493,8 @@ final boolean complete(LogonCommand logonCommand, TDSReader tdsReader) throws SQ
73947493
tdsWriter.writeBytes(secBlob, 0, secBlob.length);
73957494
}
73967495

7496+
writeUserAgentFeatureRequest(true, tdsWriter);
7497+
73977498
// AE is always ON
73987499
writeAEFeatureRequest(true, tdsWriter);
73997500

0 commit comments

Comments
 (0)