]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #4334: Mms curse expansion
authorJared Rittle (jrittle) <jrittle@cisco.com>
Thu, 20 Jun 2024 07:25:50 +0000 (07:25 +0000)
committerOleksii Shumeiko -X (oshumeik - SOFTSERVE INC at Cisco) <oshumeik@cisco.com>
Thu, 20 Jun 2024 07:25:50 +0000 (07:25 +0000)
Merge in SNORT/snort3 from ~JRITTLE/snort3:mms_curse_expansion to master

Squashed commit of the following:

commit e5e80b73e05b19ecc9ef0b4b954f4504c70de766
Author: Jared Rittle <>
Date:   Tue May 28 14:24:39 2024 -0400

    wizard: expand MMS curse

src/service_inspectors/wizard/mms_curse.cc
src/service_inspectors/wizard/mms_curse.h

index 389fbab832207d696cc8cab511ab290ca80b6dfd..e7d7554d24e8b8d03108d59bb66eac7f9f9d1b71 100644 (file)
 
 #include <assert.h>
 
-bool CurseBook::mms_curse(const uint8_t* data, unsigned len, CurseTracker* tracker)
+enum PresCtx
 {
-    // peg the tracker to MMS
-    MmsTracker& mms = tracker->mms;
+    PRES_CTX_ACSE = 1,
+    PRES_CTX_MMS = 3,
+};
 
-    // if the state is set to MMS_STATE__SEARCH it means we most likely
-    // have a split pipelined message coming through and will need to
-    // reset the state
-    if ( mms.state == MMS_STATE__SEARCH )
+static bool verify_search_depth_idx( unsigned len, uint32_t idx, uint32_t max_depth_idx )
+{
+    return idx + max_depth_idx < len;
+}
+
+static uint32_t search_for_osi_session_spdu( MmsTracker& mms, const uint8_t* data, uint32_t idx )
+{
+    switch ( data[idx] )
     {
-        mms.state = mms.last_state;
+    // check for a known MMS message tag in the event Session/Pres/ACSE aren't used
+    case MMS_CONFIRMED_REQUEST_TAG:    // fallthrough intentional
+    case MMS_CONFIRMED_RESPONSE_TAG:   // fallthrough intentional
+    case MMS_CONFIRMED_ERROR_TAG:      // fallthrough intentional
+    case MMS_UNCONFIRMED_TAG:          // fallthrough intentional
+    case MMS_REJECT_TAG:               // fallthrough intentional
+    case MMS_CANCEL_REQUEST_TAG:       // fallthrough intentional
+    case MMS_CANCEL_RESPONSE_TAG:      // fallthrough intentional
+    case MMS_CANCEL_ERROR_TAG:         // fallthrough intentional
+    case MMS_INITIATE_REQUEST_TAG:     // fallthrough intentional
+    case MMS_INITIATE_RESPONSE_TAG:    // fallthrough intentional
+    case MMS_INITIATE_ERROR_TAG:       // fallthrough intentional
+    case MMS_CONCLUDE_REQUEST_TAG:     // fallthrough intentional
+    case MMS_CONCLUDE_RESPONSE_TAG:    // fallthrough intentional
+    case MMS_CONCLUDE_ERROR_TAG:
+    {
+        // when it looks like MMS this early, drop the index back one and push processing down to the full MMS parsing
+        // taking this approach as there are other paths into the MMS determination
+        if ( idx > 0 )
+        {
+            idx--;
+            mms.state = MMS_STATE__MMS;
+            break;
+        }
+
+        // default to MMS not found
+        mms.state = MMS_STATE__NOT_FOUND;
+        break;
     }
 
-    // define all known MMS tags to check for
+    // if mms isn't found, search for a supported OSI Session layer
+
+    // check for the Confirmed Request/Response path
+    case MMS_OSI_SESSION_SPDU_GT_DT:
+    {
+        mms.state = MMS_STATE__OSI_SESSION_SPDU_GT_LEN;
+        break;
+    }
+
+    // check for the Initiate Request path
+    case MMS_OSI_SESSION_SPDU_CN:
+    {
+        mms.state = MMS_STATE__OSI_SESSION_SPDU_CN_LEN;
+        break;
+    }
+
+    // check for the Initiate Response path
+    case MMS_OSI_SESSION_SPDU_AC:
+    {
+        mms.state = MMS_STATE__OSI_SESSION_SPDU_AC_LEN;
+        break;
+    }
+
+    // if neither are found, it is most likely not MMS
+    default:
+    {
+        mms.state = MMS_STATE__NOT_FOUND;
+        break;
+    }
+    }
+    return idx;
+}
+
+static uint32_t search_for_pres_ctx( MmsTracker& mms, const uint8_t* data, unsigned len, uint32_t idx )
+{
+    // define some constants for search windows
+    constexpr uint32_t fully_encoded_data_window = 3;
+    constexpr uint32_t pres_ctx_window = 3;
+    constexpr uint32_t max_depth_idx = fully_encoded_data_window + pres_ctx_window;
+
+    // try to determine if the ACSE layer exists
+    // len reduction is to allow space for the forward looking checks
+    bool ctx_likely = false;
+    for ( uint32_t init_byte = idx; ( init_byte < len - max_depth_idx ) && !ctx_likely; init_byte++)
+    {
+        // require the user-data fully encoded data tag ( | 61 | )
+        if ( data[init_byte] != 0x61 )
+        {
+            continue;
+        }
+
+        // make sure there is still enough space left in the buffer for the forward search
+        // allow up to two bytes for the fully encoded data field
+        if ( !verify_search_depth_idx( len, init_byte, max_depth_idx ) )
+        {
+            // not enough data to process
+            assert( !ctx_likely );
+            break;
+        }
+
+        for ( uint32_t encode_tag_shift = 1; ( encode_tag_shift < fully_encoded_data_window ) && !ctx_likely; encode_tag_shift++ )
+        {
+            uint32_t encode_tag_byte = init_byte + encode_tag_shift;
+            // look for the ' fully encoded data ' user data tag ( | 30 | );
+            if ( data[encode_tag_byte] != 0x30 )
+            {
+                continue;
+            }
+
+            for ( uint32_t pres_ctx_shift = 1; ( pres_ctx_shift < + pres_ctx_window ) && !ctx_likely; pres_ctx_shift++ )
+            {
+                // look for the presentation context tag and length ( | 02 01 | )
+                bool ctx_b1 = data[encode_tag_byte + pres_ctx_shift] == 0x02;
+                bool ctx_b2 = data[encode_tag_byte + pres_ctx_shift + 1] == 0x01;
+                if ( ctx_b1 && ctx_b2 )
+                {
+                    switch ( data[encode_tag_byte + pres_ctx_shift + 2] )
+                    {
+                    // set the state accordingly when the OSI ACSE presentation context ( | 01 | ) has been found
+                    case PresCtx::PRES_CTX_ACSE:
+                    {
+                        // place the index at the last byte of the presentation context
+                        idx = init_byte + encode_tag_shift + pres_ctx_shift + 2;
+                        mms.state = MMS_STATE__OSI_ACSE;
+                        ctx_likely = true;
+                        break;
+                    }
+                    // set the state accordingly when the MMS presentation context ( | 03 | ) has been found
+                    case PresCtx::PRES_CTX_MMS:
+                    {
+                        // place the index at the last byte of the presentation context
+                        idx = init_byte + encode_tag_shift + pres_ctx_shift + 2;
+                        mms.state = MMS_STATE__MMS;
+                        ctx_likely = true;
+                        break;
+                    }
+                    // no default as we want to keep looking if an acceptable context is not found
+                    }
+                }
+            }
+        }
+    }
+    return idx;
+}
+
+static uint32_t search_for_osi_session_spdu_params( MmsTracker& mms, const uint8_t* data, unsigned len, uint32_t idx )
+{
     enum
     {
-        MMS_CONFIRMED_REQUEST_TAG    = 0xA0,
-        MMS_CONFIRMED_RESPONSE_TAG   = 0xA1,
-        MMS_CONFIRMED_ERROR_TAG      = 0xA2,
-        MMS_UNCONFIRMED_TAG          = 0xA3,
-        MMS_REJECT_TAG               = 0xA4,
-        MMS_CANCEL_REQUEST_TAG       = 0x85,
-        MMS_CANCEL_RESPONSE_TAG      = 0x86,
-        MMS_CANCEL_ERROR_TAG         = 0xA7,
-        MMS_INITIATE_REQUEST_TAG     = 0xA8,
-        MMS_INITIATE_RESPONSE_TAG    = 0xA9,
-        MMS_INITIATE_ERROR_TAG       = 0xAA,
-        MMS_CONCLUDE_REQUEST_TAG     = 0x8B,
-        MMS_CONCLUDE_RESPONSE_TAG    = 0x8C,
-        MMS_CONCLUDE_ERROR_TAG       = 0xAD,
+        CN_SPDU_PARAM__CONNECT_ACCEPT_ITEM = 0x05,
+        CN_SPDU_PARAM__SESSION_REQUIREMENT = 0x14,
+        CN_SPDU_PARAM__SESSION_USER_DATA = 0xC1,
     };
 
-    uint32_t idx = 0;
-    while ( idx < len )
+    // len reduction is to allow space for the forward looking checks
+    while ( idx < len - 3 )
     {
-        switch ( mms.state )
+        // check for the possibility of a connect accept item
+        if ( data[idx] == CN_SPDU_PARAM__CONNECT_ACCEPT_ITEM )
         {
-            case MMS_STATE__TPKT_VER:
+            // track that the item was found
+            mms.connect_accept_item_likely = true;
+        }
+        // check for the possibility of a session requirement
+        else if ( data[idx] == CN_SPDU_PARAM__SESSION_REQUIREMENT )
+        {
+            // track that the item was found
+            mms.session_requirement_likely = true;
+        }
+        // check for the possibility of a session user data item
+        else if ( data[idx] == CN_SPDU_PARAM__SESSION_USER_DATA )
+        {
+            // when this has been found and there is a good chance all of the other required items exist, move on to look for mms
+            if ( mms.connect_accept_item_likely &&
+                mms.session_requirement_likely )
             {
-                mms.state = MMS_STATE__TPKT_RES;
+                mms.state = MMS_STATE__OSI_SESSION_SPDU_USER_DATA_LEN;
                 break;
             }
-
-            case MMS_STATE__TPKT_RES:
+            // otherwise it is unlikely that this is mms
+            else
             {
-                mms.state = MMS_STATE__TPKT_LEN1;
+                mms.state = MMS_STATE__NOT_FOUND;
                 break;
             }
+        }
+        // no else case as we want to allow for the possibility of non-standard items
+
+        // increment the index to look at the item length field
+        idx++;
+        // increment the index to the end of the item data
+        idx += data[idx];
+        // increment the index to the start of the next item
+        idx++;
+
+        // if the index has gotten larger than the available buffer, bail
+        if ( idx >= len )
+        {
+            mms.state = MMS_STATE__NOT_FOUND;
+            break;
+        }
+    }
+    return idx;
+}
+
+static uint32_t search_for_osi_acse_type( MmsTracker& mms, const uint8_t* data, unsigned len, uint32_t idx )
+{
+    enum
+    {
+        OSI_ACSE_AARQ = 0x60,
+        OSI_ACSE_AARE = 0x61,
+    };
 
-            case MMS_STATE__TPKT_LEN1:
+    constexpr uint32_t max_search_depth_idx = 3;
+    if ( verify_search_depth_idx( len, idx, max_search_depth_idx ) )
+    {
+        // when ACSE is likely found, do processing at that layer to determine if MMS is being transported
+        // length field could be 1-2 bytes depending on encoding
+        for ( uint32_t j = 0; j < max_search_depth_idx; j++ )
+        {
+            // look for either the AARQ ( | 60 | ) or AARE ( | 61 | ) tag
+            if ( data[idx+j] == OSI_ACSE_AARQ || data[idx+j] == OSI_ACSE_AARE )
             {
-                mms.state = MMS_STATE__TPKT_LEN2;
+                mms.state = MMS_STATE__OSI_ACSE_DATA;
+                idx += j;
                 break;
             }
+        }
+    }
+    return idx;
+}
 
-            case MMS_STATE__TPKT_LEN2:
+static uint32_t search_for_osi_acse_data( MmsTracker& mms, const uint8_t* data, unsigned len, uint32_t idx )
+{
+    // this will likely be a good distance from the current position
+    // minus three is to give space for the forward checks for the direct and indirect reference
+    for ( uint32_t k = 0; idx+k < len-3; k++ )
+    {
+        // look for the MMS presentation context ( | 02 01 03 | )
+        if ( data[idx+k] == 0x02 && data[idx+k+1] == 0x01 && data[idx+k+2] == 0x03 )
+        {
+            mms.state = MMS_STATE__MMS;
+            // increment the index to the end of the mms context id reference
+            idx += k+2;
+            break;
+        }
+    }
+    return idx;
+}
+
+static uint32_t search_for_mms( MmsTracker& mms, const uint8_t* data, unsigned len, uint32_t idx )
+{
+    constexpr uint32_t max_search_depth_idx = 2;
+    if ( verify_search_depth_idx( len, idx, max_search_depth_idx ) )
+    {
+        // search within the next two bytes to determine if mms is likely
+        for ( uint32_t j = 0; j < max_search_depth_idx; j++ )
+        {
+            // for each remaining byte check to see if it is in the known tag map
+            switch ( data[idx+j] )
             {
-                mms.state = MMS_STATE__COTP_LEN;
+            case MMS_CONFIRMED_REQUEST_TAG:
+            {
+                mms.state = MMS_STATE__MMS_CONFIRMED_REQUEST;
+                idx += j;
                 break;
             }
-
-            case MMS_STATE__COTP_LEN:
+            case MMS_CONFIRMED_RESPONSE_TAG:   // fallthrough intentional
+            case MMS_CONFIRMED_ERROR_TAG:      // fallthrough intentional
+            case MMS_UNCONFIRMED_TAG:          // fallthrough intentional
+            case MMS_REJECT_TAG:               // fallthrough intentional
+            case MMS_CANCEL_REQUEST_TAG:       // fallthrough intentional
+            case MMS_CANCEL_RESPONSE_TAG:      // fallthrough intentional
+            case MMS_CANCEL_ERROR_TAG:         // fallthrough intentional
+            case MMS_INITIATE_REQUEST_TAG:     // fallthrough intentional
+            case MMS_INITIATE_RESPONSE_TAG:    // fallthrough intentional
+            case MMS_INITIATE_ERROR_TAG:       // fallthrough intentional
+            case MMS_CONCLUDE_REQUEST_TAG:     // fallthrough intentional
+            case MMS_CONCLUDE_RESPONSE_TAG:    // fallthrough intentional
+            case MMS_CONCLUDE_ERROR_TAG:
             {
-                mms.state = MMS_STATE__COTP_PDU;
+                // if an MMS tag exists in the remaining data,
+                // hand off to the MMS service inspector
+                mms.state = MMS_STATE__FOUND;
+                idx += j;
                 break;
             }
+            // no default as we want to keep searching if a result wasn't found in this loop
+            }
 
-            case MMS_STATE__COTP_PDU:
+            // move on to the next case when a state has been set
+            if ( mms.state != MMS_STATE__NOT_FOUND )
             {
-                // 7 6 5 4 3 2 1 0
-                // ---------------
-                // . . . . x x x x   Destination Reference
-                // x x x x . . . .   PDU Type
-                const uint32_t MMS_COTP_PDU_DT_DATA = 0x0F;
+                break;
+            }
+        }
+    }
+    return idx;
+}
 
-                if ( data[idx] >> 0x04 != MMS_COTP_PDU_DT_DATA )
-                {
-                    mms.state = MMS_STATE__NOT_FOUND;
-                    break;
-                }
+static uint32_t search_for_mms_confirmed_request( MmsTracker& mms, const uint8_t* data, unsigned len, uint32_t idx )
+{
+    // default to MMS not found
+    mms.state = MMS_STATE__NOT_FOUND;
+
+    // confirmed service request types
+    enum
+    {
+        DEFINE_NAMED_TYPE_MESSAGE = 0xAE,
+        DEFINE_NAMED_VARIABLE_MESSAGE = 0xA7,
+        DEFINE_NAMED_VARIABLE_LIST_MESSAGE = 0xAB,
+        DEFINE_SCATTERED_ACCESS_MESSAGE = 0xA8,
+        DEFINE_SEMAPHORE_MESSAGE = 0xB5,
+        DELETE_NAMED_TYPE_MESSAGE = 0xB0,
+        DELETE_NAMED_VARIABLE_LIST_MESSAGE = 0xAD,
+        DELETE_SEMAPHORE_MESSAGE = 0xB6,
+        DELETE_VARIABLE_ACCESS_MESSAGE = 0xAA,
+        DOWNLOAD_SEGMENT_MESSAGE = 0x9B,
+        GET_NAME_LIST_MESSAGE = 0xA1,
+        GET_NAMED_TYPE_ATTRIBUTES_MESSAGE = 0xAF,
+        GET_NAMED_VARIABLE_LIST_ATTRIBUTES_MESSAGE = 0xAC,
+        GET_SCATTERED_ACCESS_ATTRIBUTES_MESSAGE = 0xA9,
+        GET_VARIABLE_ACCESS_ATTRIBUTES_MESSAGE = 0xA6,
+        IDENTIFY_MESSAGE = 0x82,
+        INITIATE_DOWNLOAD_SEQUENCE_MESSAGE = 0xBA,
+        INITIATE_UPLOAD_SEQUENCE_MESSAGE = 0x9D,
+        INPUT_MESSAGE = 0xB1,
+        OUTPUT_MESSAGE = 0xB2,
+        READ_MESSAGE = 0xA4,
+        RELINQUISH_CONTROL_MESSAGE = 0xB4,
+        RENAME_MESSAGE = 0xA3,
+        REPORT_POOL_SEMAPHORE_STATUS_MESSAGE = 0xB8,
+        REPORT_SEMAPHORE_ENTRY_STATUS_MESSAGE = 0xB9,
+        REPORT_SEMAPHORE_STATUS_MESSAGE = 0xB7,
+        STATUS_MESSAGE = 0x80,
+        TAKE_CONTROL_MESSAGE = 0xB3,
+        TERMINATE_DOWNLOAD_SEQUENCE_MESSAGE = 0xBC,
+        UPLOAD_SEGMENT_MESSAGE = 0x9E,
+        WRITE_MESSAGE = 0xA5,
+        EXPANSION_9F = 0x9F,
+        EXPANSION_BF = 0xBF,
+    };
 
-                mms.state = MMS_STATE__COTP_TPDU_NUM;
+    // look for a known Confirmed Service Request 4-6 bytes away
+    constexpr uint32_t max_search_depth_idx = 6;
+    if ( verify_search_depth_idx( len, idx, max_search_depth_idx ) )
+    {
+        for ( uint32_t j = 3; j <= max_search_depth_idx; j++ )
+        {
+            // check for any of the single byte service tags
+            switch ( data[idx+j] )
+            {
+            case DEFINE_NAMED_TYPE_MESSAGE: // fallthrough intentional
+            case DEFINE_NAMED_VARIABLE_MESSAGE: // fallthrough intentional
+            case DEFINE_NAMED_VARIABLE_LIST_MESSAGE: // fallthrough intentional
+            case DEFINE_SCATTERED_ACCESS_MESSAGE: // fallthrough intentional
+            case DEFINE_SEMAPHORE_MESSAGE: // fallthrough intentional
+            case DELETE_NAMED_TYPE_MESSAGE: // fallthrough intentional
+            case DELETE_NAMED_VARIABLE_LIST_MESSAGE: // fallthrough intentional
+            case DELETE_SEMAPHORE_MESSAGE: // fallthrough intentional
+            case DELETE_VARIABLE_ACCESS_MESSAGE: // fallthrough intentional
+            case DOWNLOAD_SEGMENT_MESSAGE: // fallthrough intentional
+            case GET_NAME_LIST_MESSAGE: // fallthrough intentional
+            case GET_NAMED_TYPE_ATTRIBUTES_MESSAGE: // fallthrough intentional
+            case GET_NAMED_VARIABLE_LIST_ATTRIBUTES_MESSAGE: // fallthrough intentional
+            case GET_SCATTERED_ACCESS_ATTRIBUTES_MESSAGE: // fallthrough intentional
+            case GET_VARIABLE_ACCESS_ATTRIBUTES_MESSAGE: // fallthrough intentional
+            case IDENTIFY_MESSAGE: // fallthrough intentional
+            case INITIATE_DOWNLOAD_SEQUENCE_MESSAGE: // fallthrough intentional
+            case INITIATE_UPLOAD_SEQUENCE_MESSAGE: // fallthrough intentional
+            case INPUT_MESSAGE: // fallthrough intentional
+            case OUTPUT_MESSAGE: // fallthrough intentional
+            case READ_MESSAGE: // fallthrough intentional
+            case RELINQUISH_CONTROL_MESSAGE: // fallthrough intentional
+            case RENAME_MESSAGE: // fallthrough intentional
+            case REPORT_POOL_SEMAPHORE_STATUS_MESSAGE: // fallthrough intentional
+            case REPORT_SEMAPHORE_ENTRY_STATUS_MESSAGE: // fallthrough intentional
+            case REPORT_SEMAPHORE_STATUS_MESSAGE: // fallthrough intentional
+            case STATUS_MESSAGE: // fallthrough intentional
+            case TAKE_CONTROL_MESSAGE: // fallthrough intentional
+            case TERMINATE_DOWNLOAD_SEQUENCE_MESSAGE: // fallthrough intentional
+            case UPLOAD_SEGMENT_MESSAGE: // fallthrough intentional
+            case WRITE_MESSAGE: // fallthrough intentional
+            case EXPANSION_9F: // fallthrough intentional
+            case EXPANSION_BF:
+            {
+                idx += j;
+                mms.state = MMS_STATE__FOUND;
                 break;
             }
+            // no default as we want to keep searching if a result wasn't found in this loop
+            }
 
-            case MMS_STATE__COTP_TPDU_NUM:
+            // move on to the next case when a state has been set
+            if ( mms.state != MMS_STATE__NOT_FOUND )
             {
-                mms.state = MMS_STATE__OSI_SESSION_SPDU;
                 break;
             }
+        }
+    }
 
-            case MMS_STATE__OSI_SESSION_SPDU:
+    return idx;
+}
+
+bool CurseBook::mms_curse( const uint8_t* data, unsigned len, CurseTracker* tracker )
+{
+    // peg the tracker to MMS
+    MmsTracker& mms = tracker->mms;
+
+    // if the state is set to MMS_STATE__SEARCH it means we most likely
+    // have a split pipelined message coming through and will need to
+    // reset the state
+    if ( mms.state == MMS_STATE__SEARCH )
+    {
+        mms.state = mms.last_state;
+    }
+
+    uint32_t idx = 0;
+    while ( idx < len )
+    {
+        switch ( mms.state )
+        {
+        case MMS_STATE__TPKT_VER:
+        {
+            // MMS is only known to run over version | 03 |
+            if ( data[idx] != 0x03 )
             {
-                // define all known OSI Session layer SPDU tags to check
-                enum
-                {
-                    MMS_OSI_SESSION_SPDU_GT_DT = 0x01,
-                    MMS_OSI_SESSION_SPDU_CN = 0x0D,
-                    MMS_OSI_SESSION_SPDU_AC = 0x0E,
-                };
+                mms.state = MMS_STATE__NOT_FOUND;
+                break;
+            }
 
-                switch ( data[idx] )
-                {
-                    // check for a known MMS message tag in the event Session/Pres/ACSE aren't used
-                    case MMS_CONFIRMED_REQUEST_TAG:    // fallthrough intentional
-                    case MMS_CONFIRMED_RESPONSE_TAG:   // fallthrough intentional
-                    case MMS_CONFIRMED_ERROR_TAG:      // fallthrough intentional
-                    case MMS_UNCONFIRMED_TAG:          // fallthrough intentional
-                    case MMS_REJECT_TAG:               // fallthrough intentional
-                    case MMS_CANCEL_REQUEST_TAG:       // fallthrough intentional
-                    case MMS_CANCEL_RESPONSE_TAG:      // fallthrough intentional
-                    case MMS_CANCEL_ERROR_TAG:         // fallthrough intentional
-                    case MMS_INITIATE_REQUEST_TAG:     // fallthrough intentional
-                    case MMS_INITIATE_RESPONSE_TAG:    // fallthrough intentional
-                    case MMS_INITIATE_ERROR_TAG:       // fallthrough intentional
-                    case MMS_CONCLUDE_REQUEST_TAG:     // fallthrough intentional
-                    case MMS_CONCLUDE_RESPONSE_TAG:    // fallthrough intentional
-                    case MMS_CONCLUDE_ERROR_TAG:
-                    {
-                        // if an MMS tag exists in the remaining data,
-                        // hand off to the MMS service inspector
-                        mms.state = MMS_STATE__FOUND;
-                        break;
-                    }
+            mms.state = MMS_STATE__TPKT_RES;
+            break;
+        }
 
-                    // if mms isn't found, search for an OSI Session layer
-                    case MMS_OSI_SESSION_SPDU_GT_DT: // fallthrough intentional
-                    case MMS_OSI_SESSION_SPDU_CN:    // fallthrough intentional
-                    case MMS_OSI_SESSION_SPDU_AC:
-                    {
-                        mms.state = MMS_STATE__MMS;
-                        break;
-                    }
+        case MMS_STATE__TPKT_RES:
+        {
+            mms.state = MMS_STATE__TPKT_LEN1;
+            break;
+        }
 
-                    // if neither are found, it is most likely not MMS
-                    default:
-                    {
-                        mms.state = MMS_STATE__NOT_FOUND;
-                    }
-                }
+        case MMS_STATE__TPKT_LEN1:
+        {
+            mms.state = MMS_STATE__TPKT_LEN2;
+            break;
+        }
 
+        case MMS_STATE__TPKT_LEN2:
+        {
+            mms.state = MMS_STATE__COTP_LEN;
+            break;
+        }
+
+        case MMS_STATE__COTP_LEN:
+        {
+            mms.state = MMS_STATE__COTP_PDU;
+            break;
+        }
+
+        case MMS_STATE__COTP_PDU:
+        {
+            // 7 6 5 4 3 2 1 0
+            // ---------------
+            // . . . . x x x x   Destination Reference
+            // x x x x . . . .   PDU Type
+            constexpr uint32_t MMS_COTP_PDU_DT_DATA = 0x0F;
+
+            if ( data[idx] >> 0x04 != MMS_COTP_PDU_DT_DATA )
+            {
+                mms.state = MMS_STATE__NOT_FOUND;
                 break;
             }
 
-            case MMS_STATE__MMS:
-            {
-                // loop through the remaining bytes in the buffer checking for known MMS tags
-                for ( uint32_t i=idx; i < len; i++ )
-                {
-                    // for each remaining byte check to see if it is in the known tag map
-                    switch ( data[i] )
-                    {
-                        case MMS_CONFIRMED_REQUEST_TAG:    // fallthrough intentional
-                        case MMS_CONFIRMED_RESPONSE_TAG:   // fallthrough intentional
-                        case MMS_CONFIRMED_ERROR_TAG:      // fallthrough intentional
-                        case MMS_UNCONFIRMED_TAG:          // fallthrough intentional
-                        case MMS_REJECT_TAG:               // fallthrough intentional
-                        case MMS_CANCEL_REQUEST_TAG:       // fallthrough intentional
-                        case MMS_CANCEL_RESPONSE_TAG:      // fallthrough intentional
-                        case MMS_CANCEL_ERROR_TAG:         // fallthrough intentional
-                        case MMS_INITIATE_REQUEST_TAG:     // fallthrough intentional
-                        case MMS_INITIATE_RESPONSE_TAG:    // fallthrough intentional
-                        case MMS_INITIATE_ERROR_TAG:       // fallthrough intentional
-                        case MMS_CONCLUDE_REQUEST_TAG:     // fallthrough intentional
-                        case MMS_CONCLUDE_RESPONSE_TAG:    // fallthrough intentional
-                        case MMS_CONCLUDE_ERROR_TAG:
-                        {
-                            // if an MMS tag exists in the remaining data,
-                            // hand off to the MMS service inspector
-                            mms.state = MMS_STATE__FOUND;
-                            break;
-                        }
-                        // no default here as it we don't know when we would hit
-                        // the first MMS tag without doing full parsing
-                    }
+            mms.state = MMS_STATE__COTP_TPDU_NUM;
+            break;
+        }
 
-                    // exit the loop when a state has been determined
-                    if ( mms.state == MMS_STATE__NOT_FOUND
-                        or mms.state == MMS_STATE__SEARCH
-                        or mms.state == MMS_STATE__FOUND )
-                    {
-                        break;
-                    }
-                }
+        case MMS_STATE__COTP_TPDU_NUM:
+        {
+            mms.state = MMS_STATE__OSI_SESSION_SPDU;
+            break;
+        }
+
+        case MMS_STATE__OSI_SESSION_SPDU:
+        {
+            idx = search_for_osi_session_spdu(mms, data, idx);
+            break;
+        }
 
+
+        //
+        // State path for most MMS messages
+        //
+
+        // check the length field of a GT SPDU
+        case MMS_STATE__OSI_SESSION_SPDU_GT_LEN:
+        {
+            // length should always be zero for MMS
+            if ( data[idx] != 0x00 )
+            {
+                mms.state = MMS_STATE__NOT_FOUND;
                 break;
             }
 
-            case MMS_STATE__FOUND:
-            {
-                mms.state = MMS_STATE__TPKT_VER;
+            mms.state = MMS_STATE__OSI_SESSION_SPDU_DT;
+            break;
+        }
 
-                return true;
+        // check for the tag of a DT SPDU
+        case MMS_STATE__OSI_SESSION_SPDU_DT:
+        {
+            // tag should always be | 01 | for DT
+            if ( data[idx] != MMS_OSI_SESSION_SPDU_GT_DT )
+            {
+                mms.state = MMS_STATE__NOT_FOUND;
+            break;
             }
 
-            case MMS_STATE__NOT_FOUND:
+            mms.state = MMS_STATE__OSI_SESSION_SPDU_DT_LEN;
+            break;
+        }
+
+        // check the length field of a DT SPDU
+        case MMS_STATE__OSI_SESSION_SPDU_DT_LEN:
+        {
+            // length should always be zero for MMS
+            if ( data[idx] != 0x00 )
             {
-                mms.state = MMS_STATE__TPKT_VER;
+                mms.state = MMS_STATE__NOT_FOUND;
+                break;
+            }
+
+            mms.state = MMS_STATE__OSI_PRES_USER_DATA;
+            break;
+        }
+
+        // process the User Data Presentation type
+        case MMS_STATE__OSI_PRES_USER_DATA:
+        {
+            idx = search_for_pres_ctx(mms, data, len, idx);
+            break;
+        }
+
+
+        //
+        // State path for Initiate-Request and Initiate-Response
+        //
+
+        // skip the CN SPDU length field
+        case MMS_STATE__OSI_SESSION_SPDU_CN_LEN:
+        {
+            mms.state = MMS_STATE__OSI_SESSION_SPDU_PARAMS;
+            break;
+        }
+
+        // skip the AC SPDU length field
+        case MMS_STATE__OSI_SESSION_SPDU_AC_LEN:
+        {
+            mms.state = MMS_STATE__OSI_SESSION_SPDU_PARAMS;
+            break;
+        }
+
+        // check the parameters
+        case MMS_STATE__OSI_SESSION_SPDU_PARAMS:
+        {
+            idx = search_for_osi_session_spdu_params(mms, data, len, idx);
+            break;
+        }
 
-                return false;
+        // jump over the length field
+        case MMS_STATE__OSI_SESSION_SPDU_USER_DATA_LEN:
+        {
+            mms.state = MMS_STATE__OSI_PRES_CP_CPA;
+            break;
+        }
+
+        // process a CP or CPA presentation type
+        case MMS_STATE__OSI_PRES_CP_CPA:
+        {
+            // look for the ' CP ' or ' CPA ' presentation type tag ( | 31 | );
+            if ( data[idx] != 0x31 )
+            {
+                mms.state = MMS_STATE__NOT_FOUND;
+                break;
             }
 
-            default:
+            mms.state = MMS_STATE__OSI_PRES_CP_CPA_USER_DATA_ACSE_LOCATE;
+            break;
+        }
+
+        //
+        case MMS_STATE__OSI_PRES_CP_CPA_USER_DATA_ACSE_LOCATE:
+        {
+            idx = search_for_pres_ctx(mms, data, len, idx);
+            break;
+        }
+
+        // check for ACSE
+        case MMS_STATE__OSI_ACSE:
+        {
+            // look for the presentation data values single ASN1 type ( | A0 | );
+            if ( data[idx] != 0xA0 )
             {
                 mms.state = MMS_STATE__NOT_FOUND;
-                assert(false);
                 break;
             }
+
+            mms.state = MMS_STATE__OSI_ACSE_TYPE;
+            break;
+        }
+
+        // look for a supported ACSE type tag
+        case MMS_STATE__OSI_ACSE_TYPE:
+        {
+            idx = search_for_osi_acse_type(mms, data, len, idx);
+            break;
+        }
+
+        //
+        case MMS_STATE__OSI_ACSE_DATA:
+        {
+            idx = search_for_osi_acse_data(mms, data, len, idx);
+            break;
+        }
+
+
+        //
+        // State path for MMS convergence
+        //
+
+        // Look for a byte known to represent a MMS tag
+        case MMS_STATE__MMS:
+        {
+            idx = search_for_mms(mms, data, len, idx);
+            break;
+        }
+
+        case MMS_STATE__MMS_CONFIRMED_REQUEST:
+        {
+            idx = search_for_mms_confirmed_request(mms, data, len, idx);
+            break;
+        }
+
+        case MMS_STATE__FOUND:
+        {
+            mms.state = MMS_STATE__TPKT_VER;
+
+            return true;
+        }
+
+        case MMS_STATE__NOT_FOUND:
+        {
+            mms.state = MMS_STATE__TPKT_VER;
+
+            return false;
+        }
+
+        default:
+        {
+            mms.state = MMS_STATE__NOT_FOUND;
+            assert( false );
+            break;
+        }
         }
 
         idx++;
index 77ef3c06d9a7f2052c6d6bcb70dc9607d948ff6f..d3303db0023cb6e5a341536940c015c60d3683de 100644 (file)
@@ -34,17 +34,60 @@ enum MMS_State
     MMS_STATE__COTP_PDU,
     MMS_STATE__COTP_TPDU_NUM,
     MMS_STATE__OSI_SESSION_SPDU,
+    MMS_STATE__OSI_SESSION_SPDU_GT_LEN,
+    MMS_STATE__OSI_SESSION_SPDU_DT,
+    MMS_STATE__OSI_SESSION_SPDU_DT_LEN,
+    MMS_STATE__OSI_SESSION_SPDU_CN_LEN,
+    MMS_STATE__OSI_SESSION_SPDU_AC_LEN,
+    MMS_STATE__OSI_SESSION_SPDU_PARAMS,
+    MMS_STATE__OSI_SESSION_SPDU_USER_DATA_LEN,
+    MMS_STATE__OSI_PRES_CP_CPA,
+    MMS_STATE__OSI_PRES_CP_CPA_USER_DATA_ACSE_LOCATE,
+    MMS_STATE__OSI_PRES_USER_DATA,
+    MMS_STATE__OSI_ACSE,
+    MMS_STATE__OSI_ACSE_TYPE,
+    MMS_STATE__OSI_ACSE_DATA,
     MMS_STATE__MMS,
+    MMS_STATE__MMS_CONFIRMED_REQUEST,
     MMS_STATE__FOUND,
     MMS_STATE__SEARCH,
     MMS_STATE__NOT_FOUND,
 };
 
+// define all known MMS tags to check for
+enum
+{
+    MMS_CONFIRMED_REQUEST_TAG    = 0xA0,
+    MMS_CONFIRMED_RESPONSE_TAG   = 0xA1,
+    MMS_CONFIRMED_ERROR_TAG      = 0xA2,
+    MMS_UNCONFIRMED_TAG          = 0xA3,
+    MMS_REJECT_TAG               = 0xA4,
+    MMS_CANCEL_REQUEST_TAG       = 0x85,
+    MMS_CANCEL_RESPONSE_TAG      = 0x86,
+    MMS_CANCEL_ERROR_TAG         = 0xA7,
+    MMS_INITIATE_REQUEST_TAG     = 0xA8,
+    MMS_INITIATE_RESPONSE_TAG    = 0xA9,
+    MMS_INITIATE_ERROR_TAG       = 0xAA,
+    MMS_CONCLUDE_REQUEST_TAG     = 0x8B,
+    MMS_CONCLUDE_RESPONSE_TAG    = 0x8C,
+    MMS_CONCLUDE_ERROR_TAG       = 0xAD,
+};
+
+// define all applicable OSI Session layer SPDU tags to check
+enum
+{
+    MMS_OSI_SESSION_SPDU_GT_DT = 0x01,
+    MMS_OSI_SESSION_SPDU_CN = 0x0D,
+    MMS_OSI_SESSION_SPDU_AC = 0x0E,
+};
+
 class MmsTracker
 {
 public:
     MMS_State state = MMS_State::MMS_STATE__TPKT_VER;
     MMS_State last_state = MMS_State::MMS_STATE__TPKT_VER;
+    bool connect_accept_item_likely = false;
+    bool session_requirement_likely = false;
 };
 
 #endif