1# Copyright 2022 Oticon A/S
2# SPDX-License-Identifier: Apache-2.0
3
4# BabbleSim library functions and constants targeting the 2G4 phy
5
6# Note: The following is a copy of the functionality in the BSim C-libraries
7# If/when adding to this library try to stay as close to the C library as possible
8# to make it easier to understand both worlds
9# If this file grows a lot it may be a good idea to use the existing C libraries
10# with a python wrapper; For now that is overkill though
11
12import os
13import platform
14import pwd
15import stat
16
17### Constants ###
18
19## BSim base message headers ##
20# The device wants to wait
21PB_MSG_WAIT = 0x01
22# The device is disconnecting from the phy or viceversa:
23PB_MSG_DISCONNECT = 0xFFFF
24# The device is disconnecting from the phy, and is requesting the phy to end the simulation ASAP
25PB_MSG_TERMINATE = 0xFFFE
26# The requested time tick has just finished
27PB_MSG_WAIT_END = 0x81
28
29## 2G4 Message headers from device to phy ##
30# The device will transmit
31P2G4_MSG_TX = 0x02
32# The device wants to attempt to receive
33P2G4_MSG_RX = 0x11
34# Continue receiving (the device likes the address and headers of the packet)
35P2G4_MSG_RXCONT = 0x12
36# Stop reception (the device does not likes the address or headers of the packet) => The phy will stop this reception
37P2G4_MSG_RXSTOP = 0x13
38# Do an RSSI measurement
39P2G4_MSG_RSSIMEAS = 0x14
40# Device is successfully providing a new p2G4_abort_t
41P2G4_MSG_RERESP_ABORTREEVAL = 0x15
42
43## 2G4 Message headers from phy to device ##
44# Tx completed (fully or not)
45P2G4_MSG_TX_END = 0x100
46# Poking the device to see if it wants to change its abort time
47P2G4_MSG_ABORTREEVAL = 0x101
48# Matching address found while receiving
49P2G4_MSG_RX_ADDRESSFOUND = 0x102
50# Rx completed (successfully or not)
51P2G4_MSG_RX_END = 0x103
52# RSSI measurement completed
53P2G4_MSG_RSSI_END = 0x104
54
55TIME_NEVER = 0xFFFFFFFFFFFFFFFF # UINT64_MAX
56
57# 2G4 modulations
58P2G4_MOD_BLE = 0x10 # Standard 1Mbps BLE modulation
59P2G4_MOD_BLE2M = 0x20 # Standard 2Mbps BLE
60P2G4_MOD_PROP2M = 0x21 # Proprietary 2Mbps
61P2G4_MOD_PROP3M = 0x31 # Proprietary 3Mbps
62P2G4_MOD_PROP4M = 0x41 # Proprietary 4Mbps
63
64### Functions ###
65
66# Create folder if it does not already exist
67def create_folder(path):
68    if os.access(path, os.F_OK):
69        return
70    os.mkdir(path, stat.S_IRWXG | stat.S_IRWXU)
71    if not os.access(path, os.F_OK):
72        raise Exception("Failed to create folder %s" % path)
73
74# Create
75def create_com_folder(sim_id):
76    pw_name = pwd.getpwuid(os.geteuid())[0]
77    com_path = "/tmp/bs_%s" % pw_name
78    create_folder(com_path)
79
80    com_path = com_path + "/%s" % sim_id
81    create_folder(com_path)
82    return com_path
83
84# Get process start time from /proc - note that this only works on Linux
85def get_process_start_time(pid):
86    filename = "/proc/%s/stat" % pid
87    with open(filename, "r") as file:
88        line = file.readline()
89        start_time = line.split(" ")[21] # see man 5 proc
90        return int(start_time)
91
92def lock_file_fill(lock_path, pid):
93    with open(lock_path, "w") as file:
94        file.write(str(pid) + "\n")
95        if platform.system() == "Linux":
96            starttime = get_process_start_time(pid)
97            file.write(str(starttime) + "\n")
98
99def test_and_create_lock_file(com_path, device_nbr, trace):
100    lock_path = "%s/%s.d%i.lock" % (com_path, "2G4", device_nbr)
101    my_pid = os.getpid()
102    if not os.access(lock_path, os.F_OK):
103        # The file does not exist. So unless somebody is racing us, we are safe to go
104        lock_file_fill(lock_path, my_pid)
105        return lock_path
106
107    corrupt_file = False
108    other_dead = False
109    his_starttime = 0
110
111    with open(lock_path, "r") as file:
112        try:
113            his_pid = int(file.readline())
114        except:
115            corrupt_file = True
116        if not corrupt_file and platform.system() == "Linux":
117            try:
118                his_starttime = int(file.readline())
119            except:
120                corrupt_file = True
121
122    if corrupt_file: # We are provably racing the other process, we stop
123        raise Exception("Found previous lock owned by unknown process, we may be racing each other => aborting\n")
124
125    try:
126        os.kill(his_pid, 0)
127    except:
128        other_dead = True
129
130    if other_dead == False: # a process with the pid exists
131        if platform.system() == "Linux":
132            # To be sure the pid was not reused, let's check that the process start time matches
133            other_start_time = get_process_start_time(his_pid)
134            if (his_starttime == other_start_time): # it is the same process
135                raise Exception("Found a previous, still RUNNING process w pid %s with the same sim_id and device port which would interfere with this one, aborting" % his_pid)
136            else:
137                other_dead = True
138        else:
139            raise Exception("Found a previous, still RUNNING process w pid %s with the same sim_id and device port which would interfere with this one, aborting" % his_pid)
140
141    trace.trace(3, "Found previous lock owned by DEAD process (pid was %s), will attempt to take over" % his_pid)
142    lock_file_fill(lock_path, my_pid)
143
144    return lock_path
145
146def create_fifo_if_not_there(path):
147    if os.access(path, os.F_OK):
148        return
149    try:
150        os.mkfifo(path, stat.S_IRWXG | stat.S_IRWXU)
151    except:
152        pass
153    if not os.access(path, os.F_OK):
154        raise Exception("Failed to create fifo %s" % path)
155
156# See BLUETOOTH CORE SPECIFICATION Version 5.3, Vol 6, Part B, section 1.4.1
157chIdxToCenterFreq = [
158    2404, 2406, 2408, 2410, 2412, 2414, 2416, 2418, 2420, 2422, 2424,
159    2428, 2430, 2432, 2434, 2436, 2438, 2440, 2442, 2444, 2446, 2448, 2450, 2452, 2454, 2456, 2458, 2460, 2462, 2464, 2466, 2468, 2470, 2472, 2474, 2476, 2478,
160    2402,
161    2426,
162    2480
163]
164
165# Convert from BLE channel index to the corresponding 2G4 BSim phy frequency value
166def ch_idx_to_2G4_freq(ch_idx):
167    freq = chIdxToCenterFreq[ch_idx]
168    # 2G4 frequency is offset relative to 2400 in 16 bits with fixed point notation (8.8 bits)
169    return ((freq - 2400) << 8)
170
171