1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.testutils
18 
19 import java.net.Inet4Address
20 import java.util.function.Predicate
21 
22 const val ETHER_TYPE_OFFSET = 12
23 const val ETHER_HEADER_LENGTH = 14
24 const val IPV4_PROTOCOL_OFFSET = ETHER_HEADER_LENGTH + 9
25 const val IPV4_CHECKSUM_OFFSET = ETHER_HEADER_LENGTH + 10
26 const val IPV4_DST_OFFSET = ETHER_HEADER_LENGTH + 16
27 const val IPV4_HEADER_LENGTH = 20
28 const val IPV4_UDP_OFFSET = ETHER_HEADER_LENGTH + IPV4_HEADER_LENGTH
29 const val IPV4_UDP_SRCPORT_OFFSET = IPV4_UDP_OFFSET
30 const val IPV4_UDP_DSTPORT_OFFSET = IPV4_UDP_OFFSET + 2
31 const val UDP_HEADER_LENGTH = 8
32 const val BOOTP_OFFSET = IPV4_UDP_OFFSET + UDP_HEADER_LENGTH
33 const val BOOTP_TID_OFFSET = BOOTP_OFFSET + 4
34 const val BOOTP_CLIENT_MAC_OFFSET = BOOTP_OFFSET + 28
35 const val DHCP_OPTIONS_OFFSET = BOOTP_OFFSET + 240
36 
37 const val ARP_OPCODE_OFFSET = ETHER_HEADER_LENGTH + 6
38 const val ARP_SENDER_MAC_OFFSET = ETHER_HEADER_LENGTH + 8
39 const val ARP_TARGET_IPADDR_OFFSET = ETHER_HEADER_LENGTH + 24
40 
41 /**
42  * A [Predicate] that matches a [ByteArray] if it contains the specified [bytes] at the specified
43  * [offset].
44  */
45 class OffsetFilter(val offset: Int, vararg val bytes: Byte) : Predicate<ByteArray> {
46     override fun test(packet: ByteArray) =
47             bytes.withIndex().all { it.value == packet[offset + it.index] }
48 }
49 
50 /**
51  * A [Predicate] that matches ethernet-encapped packets that contain an UDP over IPv4 datagram.
52  */
53 class IPv4UdpFilter : Predicate<ByteArray> {
54     private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x08, 0x00 /* IPv4 */).and(
55             OffsetFilter(IPV4_PROTOCOL_OFFSET, 17 /* UDP */))
56     override fun test(t: ByteArray) = impl.test(t)
57 }
58 
59 /**
60  * A [Predicate] that matches ethernet-encapped packets sent to the specified IPv4 destination.
61  */
62 class IPv4DstFilter(dst: Inet4Address) : Predicate<ByteArray> {
63     private val impl = OffsetFilter(IPV4_DST_OFFSET, *dst.address)
64     override fun test(t: ByteArray) = impl.test(t)
65 }
66 
67 /**
68  * A [Predicate] that matches ethernet-encapped ARP requests.
69  */
70 class ArpRequestFilter : Predicate<ByteArray> {
71     private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x08, 0x06 /* ARP */)
72             .and(OffsetFilter(ARP_OPCODE_OFFSET, 0x00, 0x01 /* request */))
73     override fun test(t: ByteArray) = impl.test(t)
74 }
75 
76 /**
77  * A [Predicate] that matches ethernet-encapped DHCP packets sent from a DHCP client.
78  */
79 class DhcpClientPacketFilter : Predicate<ByteArray> {
80     private val impl = IPv4UdpFilter()
81             .and(OffsetFilter(IPV4_UDP_SRCPORT_OFFSET, 0x00, 0x44 /* 68 */))
82             .and(OffsetFilter(IPV4_UDP_DSTPORT_OFFSET, 0x00, 0x43 /* 67 */))
83     override fun test(t: ByteArray) = impl.test(t)
84 }
85 
86 /**
87  * A [Predicate] that matches a [ByteArray] if it contains a ethernet-encapped DHCP packet that
88  * contains the specified option with the specified [bytes] as value.
89  */
90 class DhcpOptionFilter(val option: Byte, vararg val bytes: Byte) : Predicate<ByteArray> {
91     override fun test(packet: ByteArray): Boolean {
92         val option = findDhcpOption(packet, option) ?: return false
93         return option.contentEquals(bytes)
94     }
95 }
96 
97 /**
98  * Find a DHCP option in a packet and return its value, if found.
99  */
100 fun findDhcpOption(packet: ByteArray, option: Byte): ByteArray? =
101         findOptionOffset(packet, option, DHCP_OPTIONS_OFFSET)?.let {
102             val optionLen = packet[it + 1]
103             return packet.copyOfRange(it + 2 /* type, length bytes */, it + 2 + optionLen)
104         }
105 
106 private tailrec fun findOptionOffset(packet: ByteArray, option: Byte, searchOffset: Int): Int? {
107     if (packet.size <= searchOffset + 2 /* type, length bytes */) return null
108 
109     return if (packet[searchOffset] == option) searchOffset else {
110         val optionLen = packet[searchOffset + 1]
111         findOptionOffset(packet, option, searchOffset + 2 + optionLen)
112     }
113 }
114