Repository: secdev/scapy

Issue:
SOMEIP / SD - the sub_id field is altering the value of the method/event id
### Brief description

The field **sub_id** is only used to differentiate between **METHOD_ID** and **EVENT_ID**.
The value of the METHOD/EVENT id should still be calculated on 16bits and not on 15bits
This makes the usage for the end user more complex.
example:
event id = 0x8100 is dissected: sub_id=EVENT_ID, event_id=0x100
event id = 0xa100 is dissected: sub_id=EVENT_ID, event_id=0x2100

The users have to take scapy logic into consideration and this makes the usage more complex.
Is it possible to check the first bit without moving the offset and then read the value of the method/event id on 16bit?



### Scapy version

all

### Python version

any

### Operating system

any

### Additional environment information

_No response_

### How to reproduce

frame = Ether(raw(payload))
print(frame)

The payload is a list of bytes from Wireshark. 

### Actual result

_No response_

### Expected result

_No response_

### Related resources

_No response_


Discussion:
Thanks for your request. Could you please provide a reference to the standard, which describes the usage of the MSB to distinguish between EVENT_ID and MEHTOD_ID
The current implementation is following the requirement
[AUTOSAR_PRS_SOMEIPProtocol.pdf](https://www.autosar.org/fileadmin/user_upload/standards/foundation/21-11/AUTOSAR_PRS_SOMEIPProtocol.pdf)
section 4.1.2.2 and 4.1.2.3

But users are always focusing on the fact that Message ID = (Service ID / Method ID) [32 bit] (= 0xFFFF 8100 )
https://www.autosar.org/fileadmin/user_upload/standards/foundation/1-2/AUTOSAR_PRS_SOMEIPServiceDiscoveryProtocol.pdf
section 4.1.2.1 / Figure 4.1: SOME/IP-SD Header Format
and
_[PRS_SOMEIPSD_00152] d Service Discovery messages shall use the Method-ID (16
Bits) of 0x8100._
I don't see a simple solution to make your change possible in Scapy. 

One possibility would be, to remove the field "event_id" or "method_id" entirely and only support one or the other. 

In this way, you obtain one 16bit wide field. This sub_id field could be removed. Is this a desired change, in your opinion?
Quick solution for other readers

```
METHOD_ID = 0
EVENT_ID = 1

def compute_meth_id (s_id, ev_id):
    #Event ID from 0x8000 = 32768  
    if (s_id == EVENT_ID):
        return ev_id + 0x8000
    elif (s_id == METHOD_ID):
        return ev_id 
    raise ValueError('Packet had unknown sub_id value!')
```
@polybassa Hi, what's the state of this issue?

Resolving patch:
diff --git a/scapy/contrib/automotive/someip.py b/scapy/contrib/automotive/someip.py
--- a/scapy/contrib/automotive/someip.py
+++ b/scapy/contrib/automotive/someip.py
@@ -7,8 +7,6 @@
 # scapy.contrib.description = Scalable service-Oriented MiddlewarE/IP (SOME/IP)
 # scapy.contrib.status = loads
 
-import ctypes
-import collections
 import struct
 
 from scapy.layers.inet import TCP, UDP
@@ -16,10 +14,12 @@
 from scapy.compat import raw, orb
 from scapy.config import conf
 from scapy.packet import Packet, Raw, bind_top_down, bind_bottom_up
-from scapy.fields import XShortField, BitEnumField, ConditionalField, \
-    BitField, XBitField, IntField, XByteField, ByteEnumField, \
-    ShortField, X3BytesField, StrLenField, IPField, FieldLenField, \
-    PacketListField, XIntField
+from scapy.fields import (XShortField, ConditionalField,
+                          BitField, XBitField, XByteField, ByteEnumField,
+                          ShortField, X3BytesField, StrLenField, IPField,
+                          FieldLenField, PacketListField, XIntField,
+                          MultipleTypeField, FlagsField, IntField,
+                          XByteEnumField, BitScalingField)
 
 
 class SOMEIP(Packet):
@@ -62,11 +62,17 @@ class SOMEIP(Packet):
 
     fields_desc = [
         XShortField("srv_id", 0),
-        BitEnumField("sub_id", 0, 1, {0: "METHOD_ID", 1: "EVENT_ID"}),
-        ConditionalField(XBitField("method_id", 0, 15),
-                         lambda pkt: pkt.sub_id == 0),
-        ConditionalField(XBitField("event_id", 0, 15),
-                         lambda pkt: pkt.sub_id == 1),
+        MultipleTypeField(
+            [
+                (XShortField("sub_id", 0),
+                 (lambda pkt: False,
+                  lambda pkt, val: val < 0x8000), "method_id"),
+                (XShortField("sub_id", 0),
+                 (lambda pkt: False,
+                  lambda pkt, val: val >= 0x8000), "event_id"),
+            ],
+            XShortField("sub_id", 0),
+        ),
         IntField("len", None),
         XShortField("client_id", 0),
         XShortField("session_id", 0),
@@ -102,14 +108,29 @@ class SOMEIP(Packet):
             RET_E_MALFORMED_MSG: "E_MALFORMED_MESSAGE",
             RET_E_WRONG_MESSAGE_TYPE: "E_WRONG_MESSAGE_TYPE",
         }),
-        ConditionalField(BitField("offset", 0, 28),
-                         lambda pkt: SOMEIP._is_tp(pkt)),
-        ConditionalField(BitField("res", 0, 3),
-                         lambda pkt: SOMEIP._is_tp(pkt)),
-        ConditionalField(BitField("more_seg", 0, 1),
-                         lambda pkt: SOMEIP._is_tp(pkt))
+        ConditionalField(
+            BitScalingField("offset", 0, 28, scaling=16, unit="bytes"),
+            lambda pkt: SOMEIP._is_tp(pkt)),  # noqa: E501
+        ConditionalField(
+            BitField("res", 0, 3),
+            lambda pkt: SOMEIP._is_tp(pkt)),  # noqa: E501
+        ConditionalField(
+            BitField("more_seg", 0, 1),
+            lambda pkt: SOMEIP._is_tp(pkt)),  # noqa: E501
+        ConditionalField(PacketListField(
+            "data", [Raw()], Raw,
+            length_from=lambda pkt: pkt.len - (SOMEIP.LEN_OFFSET_TP if (SOMEIP._is_tp(pkt) and (pkt.len is None or pkt.len >= SOMEIP.LEN_OFFSET_TP)) else SOMEIP.LEN_OFFSET),  # noqa: E501
+            next_cls_cb=lambda pkt, lst, cur, remain:
+                SOMEIP.get_payload_cls_by_srv_id(pkt, lst, cur, remain)),
+            lambda pkt: SOMEIP._is_tp(pkt))  # noqa: E501
     ]
 
+    payload_cls_by_srv_id = dict()  # To be customized
+
+    @staticmethod
+    def get_payload_cls_by_srv_id(pkt, lst, cur, remain):
+        return SOMEIP.payload_cls_by_srv_id.get(pkt.srv_id, Raw)
+
     def post_build(self, pkt, pay):
         length = self.len
         if length is None:
@@ -135,14 +156,18 @@ def answers(self, other):
     @staticmethod
     def _is_tp(pkt):
         """Returns true if pkt is using SOMEIP-TP, else returns false."""
+        if isinstance(pkt, Packet):
+            return pkt.msg_type & 0x20
+        else:
+            return pkt[15] & 0x20
 
-        tp = [SOMEIP.TYPE_TP_REQUEST, SOMEIP.TYPE_TP_REQUEST_NO_RET,
-              SOMEIP.TYPE_TP_NOTIFICATION, SOMEIP.TYPE_TP_RESPONSE,
-              SOMEIP.TYPE_TP_ERROR]
+    @staticmethod
+    def _is_sd(pkt):
+        """Returns true if pkt is using SOMEIP-SD, else returns false."""
         if isinstance(pkt, Packet):
-            return pkt.msg_type in tp
+            return pkt.srv_id == 0xffff and pkt.sub_id == 0x8100
         else:
-            return pkt[15] in tp
+            return pkt[:4] == b"\xff\xff\x81\x00"
 
     def fragment(self, fragsize=1392):
         """Fragment SOME/IP-TP"""
@@ -188,6 +213,7 @@ def _bind_someip_layers():
 
 class _SDPacketBase(Packet):
     """ base class to be used among all SD Packet definitions."""
+
     def extract_padding(self, s):
         return "", s
 
@@ -205,7 +231,11 @@ def extract_padding(self, s):
 
 def _MAKE_SDENTRY_COMMON_FIELDS_DESC(type):
     return [
-        XByteField("type", type),
+        XByteEnumField("type", type, {
+            0: "FindService",
+            1: "OfferService",
+            6: "SubscribeEventgroup",
+            7: "SubscribeEventgroupACK"}),
         XByteField("index_1", 0),
         XByteField("index_2", 0),
         XBitField("n_opt_1", 0, 4),
@@ -288,7 +318,15 @@ def _sdoption_class(payload, **kargs):
 def _MAKE_COMMON_SDOPTION_FIELDS_DESC(type, length=None):
     return [
         ShortField("len", length),
-        XByteField("type", type),
+        XByteEnumField("type", type, {
+            SDOPTION_CFG_TYPE: "Configuration",
+            SDOPTION_LOADBALANCE_TYPE: "LoadBalancing",
+            SDOPTION_IP4_ENDPOINT_TYPE: "IPv4Endpoint",
+            SDOPTION_IP4_MCAST_TYPE: "IPv4MultiCast",
+            SDOPTION_IP4_SDENDPOINT_TYPE: "IPv4SDEndpoint",
+            SDOPTION_IP6_ENDPOINT_TYPE: "IPv6Endpoint",
+            SDOPTION_IP6_MCAST_TYPE: "IPv6MultiCast",
+            SDOPTION_IP6_SDENDPOINT_TYPE: "IPv6SDEndpoint"}),
         XByteField("res_hdr", 0)
     ]
 
@@ -304,7 +342,7 @@ def _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC():
 class SDOption_Config(_SDPacketBase):
     name = "Config Option"
     fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC(SDOPTION_CFG_TYPE) + [
-        StrLenField("cfg_str", "\x00", length_from=lambda pkt: pkt.len - 1)
+        StrLenField("cfg_str", b"\x00", length_from=lambda pkt: pkt.len - 1)
     ]
 
     def post_build(self, pkt, pay):
@@ -420,8 +458,7 @@ class SD(_SDPacketBase):
             p.option_array = [SDOption_Config(),SDOption_IP6_EndPoint()]
     """
     SOMEIP_MSGID_SRVID = 0xffff
-    SOMEIP_MSGID_SUBID = 0x1
-    SOMEIP_MSGID_EVENTID = 0x100
+    SOMEIP_MSGID_SUBID = 0x8100
     SOMEIP_CLIENT_ID = 0x0000
     SOMEIP_MINIMUM_SESSION_ID = 0x0001
     SOMEIP_PROTO_VER = 0x01
@@ -429,16 +466,11 @@ class SD(_SDPacketBase):
     SOMEIP_MSG_TYPE = SOMEIP.TYPE_NOTIFICATION
     SOMEIP_RETCODE = SOMEIP.RET_E_OK
 
-    _sdFlag = collections.namedtuple('Flag', 'mask offset')
-    FLAGSDEF = {
-        "REBOOT": _sdFlag(mask=0x80, offset=7),
-        "UNICAST": _sdFlag(mask=0x40, offset=6),
-        "EXPLICIT_INITIAL_DATA_CONTROL": _sdFlag(mask=0x20, offset=5),
-    }
-
     name = "SD"
     fields_desc = [
-        XByteField("flags", 0),
+        FlagsField("flags", 0, 8, [
+            "res0", "res1", "res2", "res3", "res4",
+            "EXPLICIT_INITIAL_DATA_CONTROL", "UNICAST", "REBOOT"]),
         X3BytesField("res", 0),
         FieldLenField("len_entry_array", None,
                       length_of="entry_array", fmt="!I"),
@@ -450,21 +482,6 @@ class SD(_SDPacketBase):
                         length_from=lambda pkt: pkt.len_option_array)
     ]
 
-    def get_flag(self, name):
-        name = name.upper()
-        if name in self.FLAGSDEF:
-            return ((self.flags & self.FLAGSDEF[name].mask) >>
-                    self.FLAGSDEF[name].offset)
-        else:
-            return None
-
-    def set_flag(self, name, value):
-        name = name.upper()
-        if name in self.FLAGSDEF:
-            self.flags = (self.flags &
-                          (ctypes.c_ubyte(~self.FLAGSDEF[name].mask).value)) \
-                | ((value & 0x01) << self.FLAGSDEF[name].offset)
-
     def set_entryArray(self, entry_list):
         if isinstance(entry_list, list):
             self.entry_array = entry_list
@@ -483,7 +500,6 @@ def set_optionArray(self, option_list):
               sub_id=SD.SOMEIP_MSGID_SUBID,
               client_id=SD.SOMEIP_CLIENT_ID,
               session_id=SD.SOMEIP_MINIMUM_SESSION_ID,
-              event_id=SD.SOMEIP_MSGID_EVENTID,
               proto_ver=SD.SOMEIP_PROTO_VER,
               iface_ver=SD.SOMEIP_IFACE_VER,
               msg_type=SD.SOMEIP_MSG_TYPE,
@@ -492,7 +508,6 @@ def set_optionArray(self, option_list):
 bind_bottom_up(SOMEIP, SD,
                srv_id=SD.SOMEIP_MSGID_SRVID,
                sub_id=SD.SOMEIP_MSGID_SUBID,
-               event_id=SD.SOMEIP_MSGID_EVENTID,
                proto_ver=SD.SOMEIP_PROTO_VER,
                iface_ver=SD.SOMEIP_IFACE_VER,
                msg_type=SD.SOMEIP_MSG_TYPE,


Test patch:
diff --git a/test/contrib/automotive/someip.uts b/test/contrib/automotive/someip.uts
--- a/test/contrib/automotive/someip.uts
+++ b/test/contrib/automotive/someip.uts
@@ -55,12 +55,10 @@ binstr = b"\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x01\x01\x00\x00\xde\
 assert pstr == binstr
 
 = Dissect EVENT_ID packet
-p = SOMEIP(b"\x11\x11\x81\x11\x00\x00\x00\x04\x33\x33\x44\x44\x02\x03\x04\x05")
+p = SOMEIP(b"\x11\x11\x81\x11\x00\x00\x00\x08\x33\x33\x44\x44\x02\x03\x04\x05")
 
 assert p.srv_id == 0x1111
-assert p.sub_id == 0x1
-assert p.method_id == None
-assert p.event_id == 0x0111
+assert p.sub_id == 0x8111
 assert p.client_id == 0x3333
 assert p.session_id == 0x4444
 assert p.proto_ver == 0x02
@@ -68,13 +66,12 @@ assert p.iface_ver == 0x03
 assert p.msg_type == 0x04
 assert p.retcode == 0x05
 
+
 = Dissect METHOD_ID packet
-p = SOMEIP(b"\x11\x11\x01\x11\x00\x00\x00\x04\x33\x33\x44\x44\x02\x03\x04\x05")
+p = SOMEIP(b"\x11\x11\x01\x11\x00\x00\x00\x08\x33\x33\x44\x44\x02\x03\x04\x05")
 
 assert p.srv_id == 0x1111
-assert p.sub_id == 0x0
-assert p.method_id == 0x0111
-assert p.event_id == None
+assert p.sub_id == 0x0111
 assert p.client_id == 0x3333
 assert p.session_id == 0x4444
 assert p.proto_ver == 0x02
@@ -196,27 +193,25 @@ assert p.eventgroup_id == 0x8888
 
 = Build and check flags
 p = SD()
-p.set_flag("REBOOT", 1)
+p.flags = "REBOOT"
 assert p.flags == 0x80
 
-p.set_flag("REBOOT", 0)
+p.flags = ""
 assert p.flags == 0x00
 
-p.set_flag("UNICAST", 1)
+p.flags = "UNICAST"
 assert p.flags == 0x40
 
-p.set_flag("UNICAST", 0)
+p.flags = ""
 assert p.flags == 0x00
 
-p.set_flag("EXPLICIT_INITIAL_DATA_CONTROL", 1)
+p.flags = "EXPLICIT_INITIAL_DATA_CONTROL"
 assert p.flags == 0x20
 
-p.set_flag("EXPLICIT_INITIAL_DATA_CONTROL", 0)
+p.flags = ""
 assert p.flags == 0x00
 
-p.set_flag("REBOOT", 1)
-p.set_flag("UNICAST", 1)
-p.set_flag("EXPLICIT_INITIAL_DATA_CONTROL", 1)
+p.flags = "REBOOT+UNICAST+EXPLICIT_INITIAL_DATA_CONTROL"
 assert p.flags == 0xe0
 
 + SD Get SOME/IP Packet
@@ -227,8 +222,7 @@ assert len(bytes(p)) == SOMEIP._OVERALL_LEN_NOPAYLOAD + 12
 
 = Verify constants against spec TR_SOMEIP_00250
 assert SD.SOMEIP_MSGID_SRVID == 0xffff
-assert SD.SOMEIP_MSGID_SUBID == 0x1
-assert SD.SOMEIP_MSGID_EVENTID == 0x0100
+assert SD.SOMEIP_MSGID_SUBID == 0x8100
 assert SD.SOMEIP_CLIENT_ID == 0x0000
 assert SD.SOMEIP_MINIMUM_SESSION_ID == 0x0001
 assert SD.SOMEIP_PROTO_VER == 0x01
@@ -239,7 +233,6 @@ assert SD.SOMEIP_RETCODE == 0x00
 = check that values are bound
 assert p[SOMEIP].srv_id == SD.SOMEIP_MSGID_SRVID
 assert p[SOMEIP].sub_id == SD.SOMEIP_MSGID_SUBID
-assert p[SOMEIP].event_id == SD.SOMEIP_MSGID_EVENTID
 assert p[SOMEIP].client_id == SD.SOMEIP_CLIENT_ID
 assert p[SOMEIP].session_id != 0x0000
 assert p[SOMEIP].session_id >= SD.SOMEIP_MINIMUM_SESSION_ID
@@ -728,3 +721,9 @@ _opts_check(opts)
 _opts_check(opts[::-1])
 _opts_check(opts + opts[::-1])
 
+
+= build test SOMEIP/TP
+
+p = SOMEIP(srv_id=1234, sub_id=4321, msg_type=0xff, retcode=0xff, offset=4294967040, data=[Raw(b"deadbeef")])
+
+assert p.data[0].load == b"deadbeef"