Merge "Remove unused filegroup"
diff --git a/staticlibs/device/com/android/net/module/util/PacketBuilder.java b/staticlibs/device/com/android/net/module/util/PacketBuilder.java
index 8acb296..33e5bfa 100644
--- a/staticlibs/device/com/android/net/module/util/PacketBuilder.java
+++ b/staticlibs/device/com/android/net/module/util/PacketBuilder.java
@@ -26,6 +26,7 @@
 import static com.android.net.module.util.IpUtils.udpChecksum;
 import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET;
 import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
 import static com.android.net.module.util.NetworkStackConstants.IPV6_LEN_OFFSET;
 import static com.android.net.module.util.NetworkStackConstants.TCP_CHECKSUM_OFFSET;
 import static com.android.net.module.util.NetworkStackConstants.UDP_CHECKSUM_OFFSET;
@@ -100,9 +101,9 @@
      */
     public void writeL2Header(MacAddress srcMac, MacAddress dstMac, short etherType) throws
             IOException {
-        final EthernetHeader ethv4Header = new EthernetHeader(dstMac, srcMac, etherType);
+        final EthernetHeader ethHeader = new EthernetHeader(dstMac, srcMac, etherType);
         try {
-            ethv4Header.writeToByteBuffer(mBuffer);
+            ethHeader.writeToByteBuffer(mBuffer);
         } catch (IllegalArgumentException | BufferOverflowException e) {
             throw new IOException("Error writing to buffer: ", e);
         }
@@ -218,7 +219,7 @@
      */
     @NonNull
     public ByteBuffer finalizePacket() throws IOException {
-        // Finalize IPv4 or IPv6 header.
+        // [1] Finalize IPv4 or IPv6 header.
         int ipHeaderOffset = INVALID_OFFSET;
         if (mIpv4HeaderOffset != INVALID_OFFSET) {
             ipHeaderOffset = mIpv4HeaderOffset;
@@ -234,13 +235,14 @@
             ipHeaderOffset = mIpv6HeaderOffset;
 
             // Populate the IPv6 payloadLength field.
+            // The payload length doesn't include IPv6 header length. See rfc8200 section 3.
             mBuffer.putShort(mIpv6HeaderOffset + IPV6_LEN_OFFSET,
-                    (short) (mBuffer.position() - mIpv6HeaderOffset));
+                    (short) (mBuffer.position() - mIpv6HeaderOffset - IPV6_HEADER_LEN));
         } else {
             throw new IOException("Packet is missing neither IPv4 nor IPv6 header");
         }
 
-        // Finalize TCP or UDP header.
+        // [2] Finalize TCP or UDP header.
         if (mTcpHeaderOffset != INVALID_OFFSET) {
             // Populate the TCP header checksum field.
             mBuffer.putShort(mTcpHeaderOffset + TCP_CHECKSUM_OFFSET, tcpChecksum(mBuffer,
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h b/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h
index 7801c3e..4429164 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h
@@ -60,6 +60,9 @@
     // This is a temporary hack for network stats map swap on devices running
     // 4.9 kernels. The kernel code of socket release on pf_key socket will
     // explicitly call synchronize_rcu() which is exactly what we need.
+    //
+    // Linux 4.14/4.19/5.4/5.10/5.15 (and 5.18) still have this same behaviour.
+    // see net/key/af_key.c: pfkey_release() -> synchronize_rcu()
     int pfSocket = socket(AF_KEY, SOCK_RAW | SOCK_CLOEXEC, PF_KEY_V2);
 
     if (pfSocket < 0) {
@@ -94,17 +97,12 @@
 
 static inline unsigned uncachedKernelVersion() {
     struct utsname buf;
-    int ret = uname(&buf);
-    if (ret) return 0;
+    if (uname(&buf)) return 0;
 
-    unsigned kver_major;
-    unsigned kver_minor;
-    unsigned kver_sub;
-    char unused;
-    ret = sscanf(buf.release, "%u.%u.%u%c", &kver_major, &kver_minor, &kver_sub, &unused);
-    // Check the device kernel version
-    if (ret < 3) return 0;
-
+    unsigned kver_major = 0;
+    unsigned kver_minor = 0;
+    unsigned kver_sub = 0;
+    (void)sscanf(buf.release, "%u.%u.%u", &kver_major, &kver_minor, &kver_sub);
     return KVER(kver_major, kver_minor, kver_sub);
 }
 
diff --git a/staticlibs/native/tcutils/kernelversion.h b/staticlibs/native/tcutils/kernelversion.h
index 492444a..9aab31d 100644
--- a/staticlibs/native/tcutils/kernelversion.h
+++ b/staticlibs/native/tcutils/kernelversion.h
@@ -34,20 +34,12 @@
 
 static inline unsigned uncachedKernelVersion() {
   struct utsname buf;
-  int ret = uname(&buf);
-  if (ret)
-    return 0;
+  if (uname(&buf)) return 0;
 
-  unsigned kver_major;
-  unsigned kver_minor;
-  unsigned kver_sub;
-  char discard;
-  ret = sscanf(buf.release, "%u.%u.%u%c", &kver_major, &kver_minor, &kver_sub,
-               &discard);
-  // Check the device kernel version
-  if (ret < 3)
-    return 0;
-
+  unsigned kver_major = 0;
+  unsigned kver_minor = 0;
+  unsigned kver_sub = 0;
+  (void)sscanf(buf.release, "%u.%u.%u", &kver_major, &kver_minor, &kver_sub);
   return KVER(kver_major, kver_minor, kver_sub);
 }
 
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
index 1a0752a..e40cd6b 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
@@ -24,7 +24,6 @@
 import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
 import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
 import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
-import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
 import static com.android.net.module.util.NetworkStackConstants.TCPHDR_ACK;
 import static com.android.net.module.util.NetworkStackConstants.TCP_HEADER_MIN_LEN;
 import static com.android.net.module.util.NetworkStackConstants.UDP_HEADER_LEN;
@@ -271,9 +270,8 @@
                 // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
                 //                     type='IPv6') /
                 //           scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
-                //                      fl=0x515ca, plen=48, hlim=0x40) /
+                //                      fl=0x515ca, hlim=0x40) /
                 //           scapy.UDP(sport=9876, dport=433))
-                // Note that plen(48) = ipv6hdr(40) + udphdr(8).
                 // Ether header
                 (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
                 (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
@@ -281,7 +279,7 @@
                 (byte) 0x86, (byte) 0xdd,
                 // IP header
                 (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
-                (byte) 0x00, (byte) 0x30, (byte) 0x11, (byte) 0x40,
+                (byte) 0x00, (byte) 0x08, (byte) 0x11, (byte) 0x40,
                 (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
@@ -300,10 +298,9 @@
                 // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
                 //                       type='IPv6') /
                 //           scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
-                //                      fl=0x515ca, plen=52, hlim=0x40) /
+                //                      fl=0x515ca, hlim=0x40) /
                 //           scapy.UDP(sport=9876, dport=433) /
                 //           b'\xde\xad\xbe\xef')
-                // Note that plen(52) = ipv6hdr(40) + udphdr(8) + data(4).
                 // Ether header
                 (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
                 (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
@@ -311,7 +308,7 @@
                 (byte) 0x86, (byte) 0xdd,
                 // IP header
                 (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
-                (byte) 0x00, (byte) 0x34, (byte) 0x11, (byte) 0x40,
+                (byte) 0x00, (byte) 0x0c, (byte) 0x11, (byte) 0x40,
                 (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
@@ -332,11 +329,10 @@
                 // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
                 //                       type='IPv6') /
                 //           scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
-                //                      fl=0x515ca, plen=64, hlim=0x40) /
+                //                      fl=0x515ca, hlim=0x40) /
                 //           scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
                 //                     flags='A', window=8192, urgptr=0) /
                 //           b'\xde\xad\xbe\xef')
-                // Note that plen(64) = ipv6hdr(40) + udphdr(20) + data(4).
                 // Ether header
                 (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
                 (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
@@ -344,7 +340,7 @@
                 (byte) 0x86, (byte) 0xdd,
                 // IPv6 header
                 (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
-                (byte) 0x00, (byte) 0x40, (byte) 0x06, (byte) 0x40,
+                (byte) 0x00, (byte) 0x18, (byte) 0x06, (byte) 0x40,
                 (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
@@ -368,10 +364,9 @@
                 // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
                 //                       type='IPv6') /
                 //           scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
-                //                      fl=0x515ca, plen=60, hlim=0x40) /
+                //                      fl=0x515ca, hlim=0x40) /
                 //           scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
                 //                     flags='A', window=8192, urgptr=0))
-                // Note that plen(60) = ipv6hdr(40) + udphdr(20).
                 // Ether header
                 (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
                 (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
@@ -379,7 +374,7 @@
                 (byte) 0x86, (byte) 0xdd,
                 // IPv6 header
                 (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
-                (byte) 0x00, (byte) 0x3c, (byte) 0x06, (byte) 0x40,
+                (byte) 0x00, (byte) 0x14, (byte) 0x06, (byte) 0x40,
                 (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
@@ -399,14 +394,13 @@
     private static final byte[] TEST_PACKET_IPV6HDR_TCPHDR_DATA =
             new byte[] {
                 // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
-                //                      fl=0x515ca, plen=64, hlim=0x40) /
+                //                      fl=0x515ca, hlim=0x40) /
                 //           scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
                 //                     flags='A', window=8192, urgptr=0) /
                 //           b'\xde\xad\xbe\xef')
-                // Note that plen(64) = ipv6hdr(40) + udphdr(20) + data(4).
                 // IPv6 header
                 (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
-                (byte) 0x00, (byte) 0x40, (byte) 0x06, (byte) 0x40,
+                (byte) 0x00, (byte) 0x18, (byte) 0x06, (byte) 0x40,
                 (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
@@ -428,13 +422,12 @@
     private static final byte[] TEST_PACKET_IPV6HDR_TCPHDR =
             new byte[] {
                 // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
-                //                      fl=0x515ca, plen=60, hlim=0x40) /
+                //                      fl=0x515ca, hlim=0x40) /
                 //           scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
                 //                     flags='A', window=8192, urgptr=0))
-                // Note that plen(60) = ipv6hdr(40) + udphdr(20).
                 // IPv6 header
                 (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
-                (byte) 0x00, (byte) 0x3c, (byte) 0x06, (byte) 0x40,
+                (byte) 0x00, (byte) 0x14, (byte) 0x06, (byte) 0x40,
                 (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
@@ -454,12 +447,11 @@
     private static final byte[] TEST_PACKET_IPV6HDR_UDPHDR =
             new byte[] {
                 // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
-                //                      fl=0x515ca, plen=48, hlim=0x40) /
+                //                      fl=0x515ca, hlim=0x40) /
                 //           scapy.UDP(sport=9876, dport=433))
-                // Note that plen(48) = ipv6hdr(40) + udphdr(8).
                 // IP header
                 (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
-                (byte) 0x00, (byte) 0x30, (byte) 0x11, (byte) 0x40,
+                (byte) 0x00, (byte) 0x08, (byte) 0x11, (byte) 0x40,
                 (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
@@ -476,13 +468,12 @@
     private static final byte[] TEST_PACKET_IPV6HDR_UDPHDR_DATA =
             new byte[] {
                 // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
-                //                      fl=0x515ca, plen=52, hlim=0x40) /
+                //                      fl=0x515ca, hlim=0x40) /
                 //           scapy.UDP(sport=9876, dport=433) /
                 //           b'\xde\xad\xbe\xef')
-                // Note that plen(52) = ipv6hdr(40) + udphdr(8) + data(4).
                 // IP header
                 (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
-                (byte) 0x00, (byte) 0x34, (byte) 0x11, (byte) 0x40,
+                (byte) 0x00, (byte) 0x0c, (byte) 0x11, (byte) 0x40,
                 (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
@@ -653,12 +644,10 @@
 
         final int dataLength = hasData ? DATA.limit() : 0;
         if (l4proto == IPPROTO_TCP) {
-            assertEquals(IPV6_HEADER_LEN + TCP_HEADER_MIN_LEN + dataLength,
-                    ipv6Header.payloadLength);
+            assertEquals(TCP_HEADER_MIN_LEN + dataLength, ipv6Header.payloadLength);
             assertEquals((byte) IPPROTO_TCP, ipv6Header.nextHeader);
         } else if (l4proto == IPPROTO_UDP) {
-            assertEquals(IPV6_HEADER_LEN + UDP_HEADER_LEN + dataLength,
-                    ipv6Header.payloadLength);
+            assertEquals(UDP_HEADER_LEN + dataLength, ipv6Header.payloadLength);
             assertEquals((byte) IPPROTO_UDP, ipv6Header.nextHeader);
         }
     }
diff --git a/staticlibs/tests/unit/src/com/android/testutils/DeviceInfoUtilsTest.java b/staticlibs/tests/unit/src/com/android/testutils/DeviceInfoUtilsTest.java
index f99700a..6f603d5 100644
--- a/staticlibs/tests/unit/src/com/android/testutils/DeviceInfoUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/testutils/DeviceInfoUtilsTest.java
@@ -17,6 +17,9 @@
 package com.android.testutils;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 
 import androidx.test.filters.SmallTest;
 
@@ -42,4 +45,65 @@
         assertEquals(-1, DeviceInfoUtils.compareMajorMinorVersion("3.10", "4.8.0"));
         assertEquals(-1, DeviceInfoUtils.compareMajorMinorVersion("4.7.10.10", "4.8"));
     }
+
+    @Test
+    public void testGetMajorMinorSubminorVersion() throws Exception {
+        final DeviceInfoUtils.KVersion expected = new DeviceInfoUtils.KVersion(4, 19, 220);
+        assertEquals(expected, DeviceInfoUtils.getMajorMinorSubminorVersion("4.19.220"));
+        assertEquals(expected, DeviceInfoUtils.getMajorMinorSubminorVersion("4.19.220.50"));
+        assertEquals(expected, DeviceInfoUtils.getMajorMinorSubminorVersion(
+                "4.19.220-g500ede0aed22-ab8272303"));
+
+        final DeviceInfoUtils.KVersion expected2 = new DeviceInfoUtils.KVersion(5, 17, 0);
+        assertEquals(expected2, DeviceInfoUtils.getMajorMinorSubminorVersion("5.17"));
+        assertEquals(expected2, DeviceInfoUtils.getMajorMinorSubminorVersion("5.17."));
+        assertEquals(expected2, DeviceInfoUtils.getMajorMinorSubminorVersion("5.17.beta"));
+        assertEquals(expected2, DeviceInfoUtils.getMajorMinorSubminorVersion(
+                "5.17-rc6-g52099515ca00-ab8032400"));
+
+        final DeviceInfoUtils.KVersion invalid = new DeviceInfoUtils.KVersion(0, 0, 0);
+        assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion(""));
+        assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("4"));
+        assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("4."));
+        assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("4-beta"));
+        assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("1.x.1"));
+        assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("x.1.1"));
+    }
+
+    @Test
+    public void testVersion() throws Exception {
+        final DeviceInfoUtils.KVersion v1 = new DeviceInfoUtils.KVersion(4, 8, 1);
+        final DeviceInfoUtils.KVersion v2 = new DeviceInfoUtils.KVersion(4, 8, 1);
+        final DeviceInfoUtils.KVersion v3 = new DeviceInfoUtils.KVersion(4, 8, 2);
+        final DeviceInfoUtils.KVersion v4 = new DeviceInfoUtils.KVersion(4, 9, 1);
+        final DeviceInfoUtils.KVersion v5 = new DeviceInfoUtils.KVersion(5, 8, 1);
+
+        assertEquals(v1, v2);
+        assertNotEquals(v1, v3);
+        assertNotEquals(v1, v4);
+        assertNotEquals(v1, v5);
+
+        assertEquals(0, v1.compareTo(v2));
+        assertEquals(-1, v1.compareTo(v3));
+        assertEquals(1, v3.compareTo(v1));
+        assertEquals(-1, v1.compareTo(v4));
+        assertEquals(1, v4.compareTo(v1));
+        assertEquals(-1, v1.compareTo(v5));
+        assertEquals(1, v5.compareTo(v1));
+
+        assertTrue(v2.isInRange(v1, v5));
+        assertTrue(v3.isInRange(v1, v5));
+        assertTrue(v4.isInRange(v1, v5));
+        assertFalse(v5.isInRange(v1, v5));
+        assertFalse(v1.isInRange(v3, v5));
+        assertFalse(v5.isInRange(v2, v4));
+
+        assertTrue(v2.isAtLeast(v1));
+        assertTrue(v3.isAtLeast(v1));
+        assertTrue(v4.isAtLeast(v1));
+        assertTrue(v5.isAtLeast(v1));
+        assertFalse(v1.isAtLeast(v3));
+        assertFalse(v1.isAtLeast(v4));
+        assertFalse(v1.isAtLeast(v5));
+    }
 }
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DeviceInfoUtils.java b/staticlibs/testutils/devicetests/com/android/testutils/DeviceInfoUtils.java
index 69cb5f8..1925b55 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/DeviceInfoUtils.java
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DeviceInfoUtils.java
@@ -26,7 +26,78 @@
  * Utilities for device information.
  */
 public class DeviceInfoUtils {
-    private static Pair<Integer, Integer> getVersionFromString(String version) {
+    /**
+     * Class for a three-part kernel version number.
+     */
+    public static class KVersion {
+        public final int major;
+        public final int minor;
+        public final int sub;
+
+        public KVersion(int major, int minor, int sub) {
+            this.major = major;
+            this.minor = minor;
+            this.sub = sub;
+        }
+
+        /**
+         * Compares with other version numerically.
+         *
+         * @param  other the other version to compare
+         * @return the value 0 if this == other;
+         *         a value less than 0 if this < other and
+         *         a value greater than 0 if this > other.
+         */
+        public int compareTo(final KVersion other) {
+            int res = Integer.compare(this.major, other.major);
+            if (res == 0) {
+                res = Integer.compare(this.minor, other.minor);
+            }
+            if (res == 0) {
+                res = Integer.compare(this.sub, other.sub);
+            }
+            return res;
+        }
+
+        /**
+         * At least satisfied with the given version.
+         *
+         * @param  from the start version to compare
+         * @return return true if this version is at least satisfied with the given version.
+         *         otherwise, return false.
+         */
+        public boolean isAtLeast(final KVersion from) {
+            return compareTo(from) >= 0;
+        }
+
+        /**
+         * Falls within the given range [from, to).
+         *
+         * @param  from the start version to compare
+         * @param  to   the end version to compare
+         * @return return true if this version falls within the given range.
+         *         otherwise, return false.
+         */
+        public boolean isInRange(final KVersion from, final KVersion to) {
+            return isAtLeast(from) && !isAtLeast(to);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof KVersion)) return false;
+            KVersion that = (KVersion) o;
+            return this.major == that.major
+                    && this.minor == that.minor
+                    && this.sub == that.sub;
+        }
+    };
+
+    /**
+     * Get a two-part kernel version number (major and minor) from a given string.
+     *
+     * TODO: use class KVersion.
+     */
+    private static Pair<Integer, Integer> getMajorMinorVersion(String version) {
         // Only gets major and minor number of the version string.
         final Pattern versionPattern = Pattern.compile("^(\\d+)(\\.(\\d+))?.*");
         final Matcher m = versionPattern.matcher(version);
@@ -49,10 +120,12 @@
      * @return the value 0 if s1 == s2;
      *         a value less than 0 if s1 < s2 and
      *         a value greater than 0 if s1 > s2.
+     *
+     * TODO: use class KVersion.
      */
     public static int compareMajorMinorVersion(final String s1, final String s2) {
-        final Pair<Integer, Integer> v1 = getVersionFromString(s1);
-        final Pair<Integer, Integer> v2 = getVersionFromString(s2);
+        final Pair<Integer, Integer> v1 = getMajorMinorVersion(s1);
+        final Pair<Integer, Integer> v2 = getMajorMinorVersion(s2);
 
         if (v1.first == v2.first) {
             return Integer.compare(v1.second, v2.second);
@@ -60,4 +133,28 @@
             return Integer.compare(v1.first, v2.first);
         }
     }
+
+    /**
+     * Get a three-part kernel version number (major, minor and subminor) from a given string.
+     * Any version string must at least have major and minor number. If the subminor number can't
+     * be parsed from string. Assign zero as subminor number. Invalid version is treated as
+     * version 0.0.0.
+     */
+    public static KVersion getMajorMinorSubminorVersion(final String version) {
+        // The kernel version is a three-part version number (major, minor and subminor). Get
+        // the three-part version numbers and discard the remaining stuff if any.
+        // For example:
+        //   4.19.220-g500ede0aed22-ab8272303 --> 4.19.220
+        //   5.17-rc6-g52099515ca00-ab8032400 --> 5.17.0
+        final Pattern versionPattern = Pattern.compile("^(\\d+)\\.(\\d+)(\\.(\\d+))?.*");
+        final Matcher m = versionPattern.matcher(version);
+        if (m.matches()) {
+            final int major = Integer.parseInt(m.group(1));
+            final int minor = Integer.parseInt(m.group(2));
+            final int sub = TextUtils.isEmpty(m.group(4)) ? 0 : Integer.parseInt(m.group(4));
+            return new KVersion(major, minor, sub);
+        } else {
+            return new KVersion(0, 0, 0);
+        }
+    }
 }