#1787 DMR Decoder enhancements for RAS enabled systems. Adds support for CSBKO:33 and FLCO:16 (#1794)

Co-authored-by: sheirerd <sheirerd@ainfosec.com>
This commit is contained in:
Denny Sheirer
2024-01-13 09:51:59 -05:00
committed by GitHub
parent 3c18bfdc81
commit 9d03c151af
10 changed files with 351 additions and 29 deletions

View File

@@ -66,6 +66,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasComp
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser;
@@ -142,6 +143,16 @@ public class DMRDecoderState extends TimeslotDecoderState
}
}
/**
* Indicates if the message is valid or if the Ignore CRC Checksums feature is enabled.
* @param message to check
* @return true if ignore CRC checksums or if the message is valid, meaning the message has passed CRC check.
*/
private boolean isValid(IMessage message)
{
return mIgnoreCRCChecksums || message.isValid();
}
/**
* Indicates if this decoder state has an (optional) traffic channel manager.
*/
@@ -206,21 +217,21 @@ public class DMRDecoderState extends TimeslotDecoderState
{
if(message.getTimeslot() == getTimeslot())
{
if(message instanceof VoiceMessage)
if(message instanceof VoiceMessage voice)
{
processVoice((VoiceMessage)message);
processVoice(voice);
}
else if(message instanceof DataMessage)
else if(message instanceof DataMessage data)
{
processData((DataMessage)message);
processData(data);
}
else if((message.isValid() || mIgnoreCRCChecksums) && message instanceof LCMessage)
else if(isValid(message) && message instanceof LCMessage lcMessage)
{
processLinkControl((LCMessage)message, false);
processLinkControl(lcMessage, false);
}
else if(message.isValid() && message instanceof DMRPacketMessage)
else if(isValid(message) && message instanceof DMRPacketMessage packet)
{
processPacket((DMRPacketMessage)message);
processPacket(packet);
}
else if(message instanceof UDTShortMessageService sms)
{
@@ -232,16 +243,15 @@ public class DMRDecoderState extends TimeslotDecoderState
}
}
//SLCO messages on timeslot 0 to catch capacity plus rest channel events
else if((message.isValid() || mIgnoreCRCChecksums) && message.getTimeslot() == 0 && message instanceof LCMessage)
else if(isValid(message) && message.getTimeslot() == 0 && message instanceof LCMessage lcMessage)
{
processLinkControl((LCMessage)message, false);
processLinkControl(lcMessage, false);
}
//Pass the message to the network configuration monitor, if this decoder state has a non-null instance
if(mNetworkConfigurationMonitor != null && (message.isValid() || mIgnoreCRCChecksums) &&
message instanceof DMRMessage)
if(mNetworkConfigurationMonitor != null && isValid(message) && message instanceof DMRMessage dmrMessage)
{
mNetworkConfigurationMonitor.process((DMRMessage)message);
mNetworkConfigurationMonitor.process(dmrMessage);
}
}
@@ -503,7 +513,7 @@ public class DMRDecoderState extends TimeslotDecoderState
//Process the link control message to get the identifiers
LCMessage lc = header.getLCMessage();
if(lc.isValid())
if(isValid(lc))
{
processLinkControl(lc, false);
}
@@ -520,15 +530,15 @@ public class DMRDecoderState extends TimeslotDecoderState
switch(message.getSlotType().getDataType())
{
case CSBK:
if((message.isValid() || mIgnoreCRCChecksums) && message instanceof CSBKMessage)
if(isValid(message) && message instanceof CSBKMessage csbk)
{
processCSBK((CSBKMessage)message);
processCSBK(csbk);
}
break;
case VOICE_HEADER:
if(message instanceof HeaderMessage)
if(message instanceof HeaderMessage header)
{
processVoiceHeader((HeaderMessage)message);
processVoiceHeader(header);
}
break;
case USB_DATA:
@@ -539,9 +549,9 @@ public class DMRDecoderState extends TimeslotDecoderState
case MBC_ENC_HEADER:
case DATA_ENC_HEADER:
case CHANNEL_CONTROL_ENC_HEADER:
if(message instanceof HeaderMessage)
if(message instanceof HeaderMessage header)
{
processHeader((HeaderMessage)message);
processHeader(header);
}
break;
case SLOT_IDLE:
@@ -579,7 +589,7 @@ public class DMRDecoderState extends TimeslotDecoderState
LCMessage lcMessage = terminator.getLCMessage();
if(lcMessage.isValid())
if(isValid(lcMessage))
{
processLinkControl(lcMessage, true);
}
@@ -592,7 +602,7 @@ public class DMRDecoderState extends TimeslotDecoderState
{
LCMessage lcMessage = voiceHeader.getLCMessage();
if(lcMessage.isValid())
if(isValid(lcMessage))
{
processLinkControl(lcMessage, false);
}
@@ -1133,6 +1143,23 @@ public class DMRDecoderState extends TimeslotDecoderState
}
}
break;
case FULL_CAPACITY_MAX_GROUP_VOICE_CHANNEL_USER:
if(message instanceof CapacityMaxVoiceChannelUser cmvcu)
{
if(isTerminator)
{
getIdentifierCollection().remove(Role.FROM);
getIdentifierCollection().update(cmvcu.getTalkgroup());
}
else
{
getIdentifierCollection().update(message.getIdentifiers());
ServiceOptions serviceOptions = cmvcu.getServiceOptions();
updateCurrentCall(serviceOptions.isEncrypted() ? DecodeEventType.CALL_GROUP_ENCRYPTED :
DecodeEventType.CALL_GROUP, serviceOptions.toString(), message.getTimestamp());
}
}
break;
case FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER:
if(message instanceof CapacityPlusWideAreaVoiceChannelUser)
{

View File

@@ -66,13 +66,14 @@ public class DMRMessageProcessor implements Listener<IMessage>
private VoiceSuperFrameProcessor mSuperFrameProcessor2 = new VoiceSuperFrameProcessor();
private FLCAssembler mFLCAssemblerTimeslot1 = new FLCAssembler(1);
private FLCAssembler mFLCAssemblerTimeslot2 = new FLCAssembler(2);
private MBCAssembler mMBCAssembler = new MBCAssembler();
private MBCAssembler mMBCAssembler;
private PacketSequenceAssembler mPacketSequenceAssembler;
private SLCAssembler mSLCAssembler = new SLCAssembler();
private TalkerAliasAssembler mTalkerAliasAssembler = new TalkerAliasAssembler();
private Listener<IMessage> mMessageListener;
private Map<Integer,TimeslotFrequency> mTimeslotFrequencyMap = new TreeMap<>();
private DmrCrcMaskManager mCrcMaskManager = new DmrCrcMaskManager();
private boolean mIgnoreCrcChecksums;
/**
* Constructs an instance
@@ -80,6 +81,8 @@ public class DMRMessageProcessor implements Listener<IMessage>
public DMRMessageProcessor(DecodeConfigDMR config)
{
mConfigDMR = config;
mIgnoreCrcChecksums = config.getIgnoreCRCChecksums();
mMBCAssembler = new MBCAssembler(mIgnoreCrcChecksums);
for(TimeslotFrequency timeslotFrequency: config.getTimeslotMap())
{
@@ -89,6 +92,17 @@ public class DMRMessageProcessor implements Listener<IMessage>
mPacketSequenceAssembler = new PacketSequenceAssembler();
}
/**
* Indicates if the message is valid or if the Ignore CRC Checksums feature is enabled.
*
* @param message to check
* @return true if ignore CRC checksums or if the message is valid.
*/
private boolean isValid(IMessage message)
{
return mIgnoreCrcChecksums || message.isValid();
}
/**
* Primary message processing
*/
@@ -128,7 +142,7 @@ public class DMRMessageProcessor implements Listener<IMessage>
mSuperFrameProcessor2.process(voiceMessage);
}
}
else if(message instanceof DMRBurst dmrBurst && dmrBurst.isValid())
else if(message instanceof DMRBurst dmrBurst && isValid(dmrBurst))
{
if(dmrBurst.getTimeslot() == 1)
{
@@ -243,14 +257,14 @@ public class DMRMessageProcessor implements Listener<IMessage>
}
//Reset talker alias assembler on Idle or Terminator
if(message.isValid() && (message instanceof IDLEMessage || message instanceof Terminator))
if(isValid(message) && (message instanceof IDLEMessage || message instanceof Terminator))
{
mTalkerAliasAssembler.reset(message.getTimeslot());
}
}
//Assemble Talker Alias from FLC message fragments (header & blocks 1-3)
if(message instanceof FullLCMessage flc && flc.getOpcode().isTalkerAliasOpcode() && message.isValid())
if(message instanceof FullLCMessage flc && flc.getOpcode().isTalkerAliasOpcode() && isValid(message))
{
dispatch(mTalkerAliasAssembler.process(flc));
}

View File

@@ -34,6 +34,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraTraff
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTPreamble;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTSiteState;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAloha;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxGroupVoiceChannelUpdate;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusCSBKO_60;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusDataRevertWindowAnnouncement;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusDataRevertWindowGrant;
@@ -277,7 +278,9 @@ public class CSBKMessageFactory
case MOTOROLA_CAPPLUS_DATA_WINDOW_GRANT:
csbk = new CapacityPlusDataRevertWindowGrant(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CAPMAX_GROUP_VOICE_CHANNEL_UPDATE:
csbk = new CapacityMaxGroupVoiceChannelUpdate(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CONPLUS_CSBKO_10:
csbk = new ConnectPlusOTAAnnouncement(pattern, message, cach, slotType, timestamp, timeslot);
break;

View File

@@ -77,7 +77,9 @@ public enum Opcode
MOTOROLA_CONPLUS_DATA_WINDOW_ANNOUNCEMENT(Vendor.MOTOROLA_CONNECT_PLUS, 28, "ENHANCED DATA REVERT WINDOW ANNOUNCEMENT"),
MOTOROLA_CONPLUS_DATA_WINDOW_GRANT(Vendor.MOTOROLA_CONNECT_PLUS, 29, "ENHANCED DATA REVERT WINDOW GRANT"),
MOTOROLA_CAPMAX_GROUP_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, 16, "GROUP CHANNEL USER"),
MOTOROLA_CAPMAX_ALOHA(Vendor.MOTOROLA_CAPACITY_PLUS, 25, "ALOHA"),
MOTOROLA_CAPMAX_GROUP_VOICE_CHANNEL_UPDATE(Vendor.MOTOROLA_CAPACITY_PLUS, 33, "TALKGROUP ACTIVE"),
MOTOROLA_CAPPLUS_CALL_ALERT(Vendor.MOTOROLA_CAPACITY_PLUS, 31, "CALL ALERT"),
MOTOROLA_CAPPLUS_CALL_ALERT_ACK(Vendor.MOTOROLA_CAPACITY_PLUS, 32, "CALL ALERT ACK"),

View File

@@ -0,0 +1,153 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola Capacity Max - Group Voice Channel Update (Opcode 33 / 0x21)
*
* Likely incomplete implementation.
*/
public class CapacityMaxGroupVoiceChannelUpdate extends CSBKMessage implements ITimeslotFrequencyReceiver
{
private static final int[] CHANNEL_NUMBER = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27};
private static final int[] TIMESLOT = new int[]{28}; //Logical guess - not seeing it set in the example Tier3 recording
private static final int[] TALKGROUP = new int[]{64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
private TalkgroupIdentifier mTalkgroup;
private DMRTier3Channel mChannel;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public CapacityMaxGroupVoiceChannelUpdate(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
if(hasRAS())
{
sb.append(" RAS:").append(getBPTCReservedBits());
}
sb.append(" CSBK CAP-MAX GROUP VOICE CHANNEL UPDATE TALKGROUP:").append(getTalkgroup());
sb.append(" CHANNEL:").append(getChannel());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Talkgroup that is active on the channel.
*/
public TalkgroupIdentifier getTalkgroup()
{
if(mTalkgroup == null)
{
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(TALKGROUP));
}
return mTalkgroup;
}
/**
* Channel the talkgroup is using.
* @return channel.
*/
public DMRTier3Channel getChannel()
{
if(mChannel == null)
{
mChannel = new DMRTier3Channel(getMessage().getInt(CHANNEL_NUMBER), getChannelTimeslot());
}
return mChannel;
}
/**
* Timeslot
*/
private int getChannelTimeslot()
{
return getMessage().getInt(TIMESLOT) + 1;
}
/**
* Assigns a timeslot frequency map for the DMR channel
*
* @param timeslotFrequencies that match the logical timeslots
*/
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
getChannel().apply(timeslotFrequencies);
}
@Override
public int[] getLogicalChannelNumbers()
{
return getChannel().getLogicalChannelNumbers();
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTalkgroup());
mIdentifiers.add(getChannel());
}
return mIdentifiers;
}
}

View File

@@ -105,6 +105,7 @@ public class TalkgroupVoiceChannelGrant extends ChannelGrant
sb.append(" TALKGROUP VOICE CHANNEL GRANT FM:").append(getSourceRadio());
sb.append(" TO:").append(getDestinationTalkgroup());
sb.append(" ").append(getChannel());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}

View File

@@ -37,6 +37,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnknownFullLCMe
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraTerminator;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser;
@@ -137,6 +138,9 @@ public class LCMessageFactory
case FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER:
flc = new MotorolaGroupVoiceChannelUser(message, timestamp, timeslot);
break;
case FULL_CAPACITY_MAX_GROUP_VOICE_CHANNEL_USER:
flc = new CapacityMaxVoiceChannelUser(message, timestamp, timeslot);
break;
case FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER:
flc = new CapacityPlusEncryptedVoiceChannelUser(message, timestamp, timeslot);
break;

View File

@@ -45,6 +45,7 @@ public enum LCOpcode
FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 0, "GROUP VOICE CHANNEL USER"),
FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 4, "WAN GROUP VOICE CHANNEL USER"),
FULL_CAPACITY_MAX_GROUP_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 16, "CAPMAX GROUP VOICE CHANNEL USER"),
//Observed on Cap+ Multi-Site System during an encrypted voice call
FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 32, "ENCRYPTED VOICE CHANNEL USER"),
//Observed on Cap+ Multi-Site System during an encrypted voice call

View File

@@ -0,0 +1,102 @@
package io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import java.util.ArrayList;
import java.util.List;
/**
* Capacity Max Voice Channel User (FLCO 16 / 0x10)
*/
public class CapacityMaxVoiceChannelUser extends CapacityPlusVoiceChannelUser
{
private static final int[] TALKGROUP = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] RADIO = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65,
66, 67, 68, 69, 70, 71};
private TalkgroupIdentifier mTalkgroup;
private RadioIdentifier mRadio;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance.
*
* @param message for the link control payload
* @param timestamp
* @param timeslot
*/
public CapacityMaxVoiceChannelUser(CorrectedBinaryMessage message, long timestamp, int timeslot)
{
super(message, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
if(isEncrypted())
{
sb.append(" *ENCRYPTED*");
}
if(isReservedBitSet())
{
sb.append(" *RESERVED-BIT*");
}
sb.append("FLC MOTOROLA CAPMAX GROUP VOICE CHANNEL USER FM:");
sb.append(getRadio());
sb.append(" TO:").append(getTalkgroup());
sb.append(" ").append(getServiceOptions());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Talkgroup TO ID
*/
public TalkgroupIdentifier getTalkgroup()
{
if(mTalkgroup == null)
{
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(TALKGROUP));
}
return mTalkgroup;
}
/**
* Radio FROM ID
*/
public RadioIdentifier getRadio()
{
if(mRadio == null)
{
mRadio = DMRRadio.createFrom(getMessage().getInt(RADIO));
}
return mRadio;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTalkgroup());
mIdentifiers.add(getRadio());
}
return mIdentifiers;
}
}

View File

@@ -19,6 +19,7 @@
package io.github.dsheirer.module.decode.dmr.message.data.mbc;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessageFactory;
import io.github.dsheirer.module.decode.dmr.message.data.header.MBCHeader;
@@ -34,21 +35,35 @@ public class MBCAssembler
private MBCHeader mTS2Header;
private List<MBCContinuationBlock> mTS1ContinuationBlocks = new ArrayList<>();
private List<MBCContinuationBlock> mTS2ContinuationBlocks = new ArrayList<>();
private boolean mIgnoreCrcChecksums;
/**
* Constructs an instance
*/
public MBCAssembler()
public MBCAssembler(boolean ignoreCrcChecksums)
{
mIgnoreCrcChecksums = ignoreCrcChecksums;
}
/**
* Indicates if the message is valid or if the Ignore CRC Checksums feature is enabled.
* @param message to check
* @return true if ignore CRC checksums or if the message is valid.
*/
private boolean isValid(IMessage message)
{
return mIgnoreCrcChecksums || message.isValid();
}
/**
* Processes the MBC header
* @param header to process
*/
public void process(MBCHeader header)
{
if(header != null && header.isValid())
if(header != null && isValid(header))
{
reset(header.getTimeslot());