-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'feature/gsm-pcap-logging' into develop
- Loading branch information
Showing
9 changed files
with
252 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
app/src/main/java/com/craxiom/networksurveyplus/messages/GsmSubtypes.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.craxiom.networksurveyplus.messages; | ||
|
||
/** | ||
* The GSMTAP mapping for the GSM subtypes. The ordinal value of these enums map to the value that is used in the | ||
* GSMTAP header, so don't change the order of the enum values, and don't insert any new values in the middle. | ||
* <p> | ||
* These values are pulled from Osmocom and scat. | ||
* https://github.com/osmocom/libosmocore/blob/master/include/osmocom/core/gsmtap.h | ||
* | ||
* @since 0.4.0 | ||
*/ | ||
public enum GsmSubtypes | ||
{ | ||
GSMTAP_CHANNEL_UNKNOWN, // 0x00 | ||
GSMTAP_CHANNEL_BCCH, // 0x01 | ||
GSMTAP_CHANNEL_CCCH, // 0x02 | ||
GSMTAP_CHANNEL_RACH, // 0x03 | ||
GSMTAP_CHANNEL_AGCH, // 0x04 | ||
GSMTAP_CHANNEL_PCH, // 0x05 | ||
GSMTAP_CHANNEL_SDCCH, // 0x06 | ||
GSMTAP_CHANNEL_SDCCH4, // 0x07 | ||
GSMTAP_CHANNEL_SDCCH8, // 0x08 | ||
GSMTAP_CHANNEL_TCH_F, // 0x09 | ||
GSMTAP_CHANNEL_TCH_H, // 0x0a = 10 | ||
GSMTAP_CHANNEL_PACCH, // 0x0b = 11 | ||
GSMTAP_CHANNEL_CBCH52, // 0x0c = 12 | ||
GSMTAP_CHANNEL_PDCH, // 0x0d = 13 | ||
GSMTAP_CHANNEL_PTCCH, // 0x0e = 14 | ||
GSMTAP_CHANNEL_CBCH51, // 0x0f = 15 | ||
GSMTAP_CHANNEL_VOICE_F, // 0x10 = 16 /* voice codec payload (FR/EFR/AMR) */ | ||
GSMTAP_CHANNEL_VOICE_H, // 0x11 = 17 /* voice codec payload (HR/AMR) */ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 116 additions & 0 deletions
116
app/src/main/java/com/craxiom/networksurveyplus/messages/QcdmGsmParser.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package com.craxiom.networksurveyplus.messages; | ||
|
||
import android.location.Location; | ||
|
||
import java.util.Arrays; | ||
|
||
import timber.log.Timber; | ||
|
||
/** | ||
* Contains parser methods for converting the QCDM GSM messages to various formats, like pcap records or protobuf | ||
* objects. | ||
* | ||
* @since 0.4.0 | ||
*/ | ||
public class QcdmGsmParser | ||
{ | ||
private static final int GSM_SIGNAL_HEADER_LENGTH = 3; | ||
|
||
private QcdmGsmParser() | ||
{ | ||
} | ||
|
||
/** | ||
* Given a {@link QcdmMessage} that contains a WCDMA Signaling message {@link QcdmConstants#WCDMA_SIGNALING_MESSAGES}, | ||
* convert it to a pcap record byte array that can be consumed by tools like Wireshark. | ||
* <p> | ||
* The base header structure for the GSM RR Signaling Message: | ||
* ************************************************************* | ||
* | Channel Type | Message Type | Message Length | L3 Message | | ||
* | 1 byte | 1 byte | 1 byte | n | | ||
* ************************************************************* | ||
* <p> | ||
* The code for this method was taken from SCAT: | ||
* https://github.com/fgsect/scat/blob/0c1fe579376460ba5cd42d82a556fb88cf89da61/parsers/qualcomm/diaggsmlogparser.py#L191 | ||
* | ||
* @param qcdmMessage The QCDM message to convert into a pcap record. | ||
* @param location The location to tie to the QCDM message when writing it to a pcap file. If null then no | ||
* location will be added to the PPI header. | ||
* @return The pcap record byte array to write to a pcap file, or null if the message could not be parsed. | ||
*/ | ||
public static byte[] convertGsmSignalingMessage(QcdmMessage qcdmMessage, Location location) | ||
{ | ||
Timber.v("Handling a GSM RR Signaling message"); | ||
|
||
final byte[] logPayload = qcdmMessage.getLogPayload(); | ||
|
||
final int channelTypeDir = logPayload[0] & 0xFF; | ||
//final int messageType = logPayload[1] & 0xFF; | ||
final int messageLength = logPayload[2] & 0xFF; | ||
|
||
if (logPayload.length < messageLength + GSM_SIGNAL_HEADER_LENGTH) | ||
{ | ||
Timber.e("The qcdm log payload is shorter than the defined length for a GSM signal message"); | ||
return null; | ||
} | ||
|
||
byte[] l3Message = Arrays.copyOfRange(logPayload, GSM_SIGNAL_HEADER_LENGTH, messageLength + GSM_SIGNAL_HEADER_LENGTH); | ||
|
||
// Not sure why we take the channelTypeDir and do this to get the chan, but SCAT and all the other apps do it | ||
int chan = channelTypeDir & 0x7F; | ||
|
||
final int subtype = getGsmtapGsmChannelType(chan); | ||
|
||
if (chan == 0 || chan == 4) | ||
{ | ||
// SDCCH/8 expects LAPDm header | ||
if (messageLength > 63) | ||
{ | ||
Timber.w("The GSM signal message length is longer than 63 bytes, actual length=%d", messageLength); | ||
return null; | ||
} | ||
|
||
// SACCH/8 expects SACCH L1/LAPDm header | ||
byte[] sacchL1 = chan == 4 ? new byte[]{0x00, 0x00} : new byte[]{}; | ||
|
||
l3Message = PcapUtils.concatenateByteArrays( | ||
sacchL1, // SAACH header only if it is SAACH/8 (0x88?) | ||
new byte[]{0x01}, // LAPDM Address Field | ||
new byte[]{0x03}, // LAPDM Control Field | ||
new byte[]{(byte) ((messageLength << 2) | 0x01)}, // LAPDM Length | ||
l3Message); | ||
} | ||
|
||
// Any channel type dir that has the 0x80 bit set is downlink, everything else is uplink | ||
final boolean isUplink = (channelTypeDir & 0x80) == 0x00; | ||
|
||
return PcapUtils.getGsmtapPcapRecord(GsmtapConstants.GSMTAP_TYPE_UM, l3Message, subtype, 0, | ||
isUplink, 0, 0, qcdmMessage.getSimId(), location); | ||
} | ||
|
||
/** | ||
* Converts the QCDM chan to the GSMTAP defined channel type that needs to be included in the GSMTAP pcap header. | ||
* | ||
* @param channelType The channel type found in the QCDM header. | ||
* @return The GSMTAP Channel Type / Subtype that specifies what kind of message the payload of the GSMTAP frame | ||
* contains, or 0 if a mapping was not found. | ||
*/ | ||
public static int getGsmtapGsmChannelType(int channelType) | ||
{ | ||
switch (channelType) | ||
{ | ||
// Channel Type Map | ||
case 0: | ||
return GsmSubtypes.GSMTAP_CHANNEL_SDCCH8.ordinal(); | ||
case 1: | ||
return GsmSubtypes.GSMTAP_CHANNEL_BCCH.ordinal(); | ||
case 3: // 0x03 | ||
return GsmSubtypes.GSMTAP_CHANNEL_CCCH.ordinal(); | ||
case 4: // 0x04 | ||
return 0x88; // Not sure why 4 maps to 0x88, but that is what SCAT and others do | ||
|
||
default: | ||
return 0; | ||
} | ||
} | ||
} |
Binary file not shown.
48 changes: 48 additions & 0 deletions
48
app/src/test/java/com/craxiom/networksurveyplus/GsmParserTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package com.craxiom.networksurveyplus; | ||
|
||
import android.location.Location; | ||
|
||
import com.craxiom.networksurveyplus.messages.QcdmConstants; | ||
import com.craxiom.networksurveyplus.messages.QcdmGsmParser; | ||
import com.craxiom.networksurveyplus.messages.QcdmMessage; | ||
|
||
import org.junit.Test; | ||
|
||
import java.util.Arrays; | ||
|
||
import static org.junit.Assert.assertArrayEquals; | ||
import static org.junit.Assert.assertEquals; | ||
import static org.junit.Assert.assertNotNull; | ||
|
||
/** | ||
* Tests for the GSM QCDM Parser that takes QCDM input and converts it to a PCAP record. | ||
* | ||
* @since 0.4.0 | ||
*/ | ||
public class GsmParserTest | ||
{ | ||
@Test | ||
public void testGsmQcdmMessage_signalingMessage() | ||
{ | ||
final byte[] qcdmMessageBytes = {(byte) 0x10, (byte) 0x00, (byte) 0x26, (byte) 0x00, (byte) 0x26, (byte) 0x00, (byte) 0x2f, (byte) 0x51, (byte) 0xfd, (byte) 0x19, (byte) 0x53, (byte) 0x7b, (byte) 0x87, (byte) 0x62, (byte) 0xf4, (byte) 0x00, (byte) 0x81, (byte) 0x1b, (byte) 0x17, (byte) 0x49, (byte) 0x06, (byte) 0x1b, (byte) 0x00, (byte) 0x3e, (byte) 0x62, (byte) 0xf2, (byte) 0x20, (byte) 0x1c, (byte) 0x4e, (byte) 0xd0, (byte) 0x01, (byte) 0x0a, (byte) 0x15, (byte) 0x65, (byte) 0x44, (byte) 0xb8, (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x1f, (byte) 0x01, (byte) 0x1b}; | ||
final byte[] expectedQcdmMessagePayloadBytes = {(byte) 0x81, (byte) 0x1b, (byte) 0x17, (byte) 0x49, (byte) 0x06, (byte) 0x1b, (byte) 0x00, (byte) 0x3e, (byte) 0x62, (byte) 0xf2, (byte) 0x20, (byte) 0x1c, (byte) 0x4e, (byte) 0xd0, (byte) 0x01, (byte) 0x0a, (byte) 0x15, (byte) 0x65, (byte) 0x44, (byte) 0xb8, (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x1f, (byte) 0x01, (byte) 0x1b}; | ||
final byte[] expectedPcapRecordBytes = {(byte) 0x63, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x63, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x20, (byte) 0x00, (byte) 0xe4, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x32, (byte) 0x75, (byte) 0x14, (byte) 0x00, (byte) 0x02, (byte) 0xcf, (byte) 0x14, (byte) 0x00, (byte) 0x0e, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x05, (byte) 0x21, (byte) 0x05, (byte) 0x84, (byte) 0x01, (byte) 0x8f, (byte) 0x90, (byte) 0x35, (byte) 0x3f, (byte) 0x1d, (byte) 0x61, (byte) 0x6b, (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x43, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x40, (byte) 0x11, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x12, (byte) 0x79, (byte) 0x12, (byte) 0x79, (byte) 0x00, (byte) 0x2f, (byte) 0x00, (byte) 0x00, (byte) 0x02, (byte) 0x04, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x49, (byte) 0x06, (byte) 0x1b, (byte) 0x00, (byte) 0x3e, (byte) 0x62, (byte) 0xf2, (byte) 0x20, (byte) 0x1c, (byte) 0x4e, (byte) 0xd0, (byte) 0x01, (byte) 0x0a, (byte) 0x15, (byte) 0x65, (byte) 0x44, (byte) 0xb8, (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x1f, (byte) 0x01, (byte) 0x1b}; | ||
|
||
final QcdmMessage qcdmMessage = new QcdmMessage(qcdmMessageBytes, 0); | ||
assertEquals(16, qcdmMessage.getOpCode()); | ||
assertEquals(QcdmConstants.GSM_RR_SIGNALING_MESSAGES, qcdmMessage.getLogType()); | ||
assertArrayEquals("The qcdm message bytes did not match what was expected", expectedQcdmMessagePayloadBytes, qcdmMessage.getLogPayload()); | ||
|
||
final Location location = new FakeLocation(); | ||
location.setLatitude(41.4928645); | ||
location.setLongitude(-90.1333759); | ||
location.setAltitude(152.6591); | ||
|
||
final byte[] pcapRecordBytes = QcdmGsmParser.convertGsmSignalingMessage(qcdmMessage, location); | ||
|
||
assertNotNull(pcapRecordBytes); | ||
|
||
// Ignore the first 8 bytes since it contains the record timestamp. | ||
assertArrayEquals(expectedPcapRecordBytes, Arrays.copyOfRange(pcapRecordBytes, 8, pcapRecordBytes.length)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters