forked from public/pysim
smpp2sim: make the SCP81 BIP relay work
The BIP relay ( the "handset" side for SCP81) never worked: the connect callback in handle_OpenChannel was "never called" as the fixme says, everything else was missing. Fixme cause: card APDU I/O is driven synchronously, proactive command loop lives in a blocking while loop (pySim.transport.LinkBase.send_apdu_checksw) that runs on the Twisted reactor thread. A Twisted TCP4ClientEndpoint + connectProtocol only completes when the reactor does reactor things, but the reactor thread is stuck in that loop for the whole proactive session... Fixme fix: don't fight the reactor, just drive the relay channel with a plain old blocking socket, which fits the synchronous execution model. Channel numbers now come from the command Device identities (channel_N -> low nibble) instead of the hard coded chan_nr == 1. Additionally fix two bugs found on the path to scp81 glory: - TERMINAL RESPONSE device identities are forced to terminal->UICC per TS 102 223 6.8.2 (prepare_response() inverts the command identities, which for a channel-addressed BIP command yields channel_N->UICC). - Error responses now build a valid AddlInfoBip cause, prepare_response() hard coded empty "additional information" cannot be encoded for a BIP error. And some tests based on real card interactions. Change-Id: If96c768f2e35c20ea3753e601059410121517b60
This commit is contained in:
+306
-52
@@ -17,13 +17,21 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# A ProactiveHandler with TCP sockets that backs the BIP channels,
|
||||
# so a card can run its own IP session (SCP81/HTTPS, CAT_TP, ...)
|
||||
#
|
||||
# Currently used by pySim-smpp2sim.py which connects the SMS path to its SMPP server.
|
||||
# Other drivers can pass their own sinks:
|
||||
#
|
||||
# handler = Proact(data_available_sink=..., sms_sink=...)
|
||||
# tp = init_reader(opts, proactive_handler=handler)
|
||||
|
||||
|
||||
import logging
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
|
||||
from twisted.internet import endpoints, protocol, reactor
|
||||
|
||||
from osmocom.utils import b2h, h2b
|
||||
|
||||
from pySim.transport import ProactiveHandler
|
||||
@@ -35,56 +43,181 @@ from pySim.cat import (ProactiveCommand, SendShortMessage, SMS_TPDU, SMSPPDownlo
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class TcpProtocol(protocol.Protocol):
|
||||
def dataReceived(self, data):
|
||||
pass
|
||||
|
||||
def connectionLost(self, reason):
|
||||
pass
|
||||
|
||||
|
||||
def tcp_connected_callback(p: protocol.Protocol):
|
||||
"""called by twisted TCP client."""
|
||||
logger.error("%s: connected!" % p)
|
||||
|
||||
class ProactChannel:
|
||||
"""Representation of a single protective channel."""
|
||||
"""One BIP channel, TS 102 223 class "e", backed by a blocking TCP socket.
|
||||
|
||||
Created by ProactChannels.channel_create(). A reader thread fills the Rx buffer from the
|
||||
socket, the Proact handlers drain it (RECEIVE DATA) and write to it (SEND DATA). Payload
|
||||
is opaque, TLS or CAT_TP run on the card.
|
||||
|
||||
Args:
|
||||
channels: the owning ProactChannels, notified of data arrival and of close()
|
||||
chan_nr: channel number 1..7 as used in the Device identities
|
||||
"""
|
||||
# Why blocking sockets and not Twisted endpoints, considering we have twisted?
|
||||
# The proactive-command loop lives in a blocking while-loop,
|
||||
# "pySim.transport.LinkBase.send_apdu_checksw" that runs on the Twisted reactor thread.
|
||||
# A Twisted async TCP client only makes any progress when the reactor uhh... reacts, but
|
||||
# the reactor is stuck in that loop for the whole proactive session -> the
|
||||
# connectProtocol() Deferred never fires while we are handling OPEN/SEND/RECEIVE CHANNEL.
|
||||
# Plain blocking sockets just work: connect() in handle_OpenChannel, send() in
|
||||
# handle_SendData, recv() feeding a buffer for handle_ReceiveData. No need to make it
|
||||
# harder than it has to be to handle the "massive" T0 bandwidth..
|
||||
# how much we try to read off the socket per recv()
|
||||
RECV_CHUNK = 4096
|
||||
|
||||
def __init__(self, channels: 'ProactChannels', chan_nr: int):
|
||||
self.channels = channels
|
||||
self.chan_nr = chan_nr
|
||||
self.ep = None
|
||||
self.sock = None
|
||||
# TS 102 223 says the terminal keeps an Rx buffer per channel; RECEIVE
|
||||
# DATA drains it, and it is filled asynchronously as the peer sends.
|
||||
self.rx_buf = bytearray()
|
||||
self._rx_lock = threading.Lock()
|
||||
self._reader = None
|
||||
self._closing = False
|
||||
self.peer_closed = False
|
||||
|
||||
def connect(self, host: str, port: int, timeout: float = 10.0):
|
||||
"""Open the blocking TCP socket and start the background Rx reader."""
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
s.settimeout(timeout)
|
||||
s.connect((host, port))
|
||||
# Back to blocking mode for the reader thread.
|
||||
# CLOSE CHANNEL unblocks the pending recv() via shutdown().
|
||||
s.settimeout(None)
|
||||
except OSError:
|
||||
s.close()
|
||||
raise
|
||||
self.sock = s
|
||||
self._reader = threading.Thread(target=self._rx_loop,
|
||||
name='bip-rx-%d' % self.chan_nr, daemon=True)
|
||||
self._reader.start()
|
||||
|
||||
def _rx_loop(self):
|
||||
"""Continuously read from the socket into rx_buf, like a real ME.
|
||||
|
||||
TS 102 223 7.5.10.1 says the event is raised 'only if the targeted channel buffer is
|
||||
empty when new data arrives in it', so the data available hook fires on the
|
||||
empty->non-empty transition only. That is enough: every RECEIVE DATA response tells
|
||||
the card how many bytes remain, so it keeps fetching until the buffer is empty, and
|
||||
the next event restarts it when more data arrives."""
|
||||
while not self._closing:
|
||||
try:
|
||||
data = self.sock.recv(self.RECV_CHUNK)
|
||||
except (OSError, ValueError):
|
||||
break
|
||||
if not data:
|
||||
self.peer_closed = True
|
||||
break
|
||||
with self._rx_lock:
|
||||
was_empty = len(self.rx_buf) == 0
|
||||
self.rx_buf.extend(data)
|
||||
if was_empty and not self._closing:
|
||||
self.channels.notify_data_available(self)
|
||||
|
||||
def send(self, data: bytes):
|
||||
"""Tx, write bytes to the socket == SEND DATA"""
|
||||
self.sock.sendall(data)
|
||||
|
||||
def available_rx(self) -> int:
|
||||
"""Number of bytes waiting in the Rx buffer, what RECEIVE DATA can return right now."""
|
||||
with self._rx_lock:
|
||||
return len(self.rx_buf)
|
||||
|
||||
def take_rx(self, n: int):
|
||||
"""Take up to n bytes out of the Rx buffer. Returns (bytes, bytes still remaining)."""
|
||||
with self._rx_lock:
|
||||
chunk = bytes(self.rx_buf[:n])
|
||||
del self.rx_buf[:n]
|
||||
remaining = len(self.rx_buf)
|
||||
return chunk, remaining
|
||||
|
||||
def wait_rx(self, timeout: float) -> int:
|
||||
"""wait up to timeout seconds until the rxbuf has data
|
||||
returns the number of bytes available
|
||||
Cards have a "data available" event, card free callers use
|
||||
this to wait for the echoed bytes."""
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
avail = self.available_rx()
|
||||
if avail or self.peer_closed:
|
||||
return avail
|
||||
time.sleep(0.005)
|
||||
return self.available_rx()
|
||||
|
||||
def close(self):
|
||||
"""Close the channel."""
|
||||
if self.ep:
|
||||
self.ep.disconnect()
|
||||
"""Close channel: stop reader, close socket, drop bookkeeping."""
|
||||
self._closing = True
|
||||
if self.sock is not None:
|
||||
try:
|
||||
self.sock.shutdown(socket.SHUT_RDWR)
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
self.sock.close()
|
||||
except OSError:
|
||||
pass
|
||||
# CLOSE CHANNEL synchronously handled inside the rx reader thread
|
||||
# (data-available -> ENVELOPE -> FETCH -> handle_CloseChannel -> close),
|
||||
# so close() can be called on the reader thread.
|
||||
# Joining self raises "cannot join current thread" so better skip i..
|
||||
# setting _closing + shutting down the socket already makes _rx_loop
|
||||
# return on the next iteration anyway.
|
||||
if self._reader is not None and self._reader is not threading.current_thread():
|
||||
self._reader.join(timeout=1.0)
|
||||
self.channels.channel_delete(self.chan_nr)
|
||||
|
||||
class ProactChannels:
|
||||
"""Wrapper class for maintaining state of proactive channels."""
|
||||
def __init__(self):
|
||||
"""The open BIP channels of one terminal, keyed by channel number.
|
||||
|
||||
Args:
|
||||
on_data_available: callback(chan: ProactChannel), invoked from the channel's reader
|
||||
thread when data arrives in an empty Rx buffer. Proact turns it into an
|
||||
ENVELOPE EVENT DOWNLOAD (data available).
|
||||
"""
|
||||
def __init__(self, on_data_available=None):
|
||||
self.channels = {}
|
||||
self._on_data_available = on_data_available
|
||||
|
||||
def channel_create(self) -> ProactChannel:
|
||||
"""Create a new proactive channel, allocating its integer number."""
|
||||
for i in range(1, 9):
|
||||
for i in range(1, 8):
|
||||
if not i in self.channels:
|
||||
self.channels[i] = ProactChannel(self, i)
|
||||
return self.channels[i]
|
||||
raise ValueError('Cannot allocate another channel: All channels active')
|
||||
|
||||
def channel_delete(self, chan_nr: int):
|
||||
del self.channels[chan_nr]
|
||||
"""Forget a channel, called by ProactChannel.close()."""
|
||||
self.channels.pop(chan_nr, None)
|
||||
|
||||
def notify_data_available(self, chan: ProactChannel):
|
||||
"""Run the on_data_available callback for chan, if one was given."""
|
||||
if self._on_data_available:
|
||||
self._on_data_available(chan)
|
||||
|
||||
class Proact(ProactiveHandler):
|
||||
#def __init__(self, smpp_factory):
|
||||
# self.smpp_factory = smpp_factory
|
||||
def __init__(self, sms_sink=None):
|
||||
# sms_sink(pdu) delivers an MO-SMS the card sent (SEND SHORT MESSAGE)
|
||||
# onwards; pySim-smpp2sim.py hands it to its SMPP server. None -> log
|
||||
# and drop.
|
||||
"""ProactiveHandler that answers the BIP proactive commands with TCP sockets.
|
||||
|
||||
The transport calls the handle_* methods with the decoded proactive command and posts
|
||||
the returned IE list as TERMINAL RESPONSE.
|
||||
|
||||
Args:
|
||||
data_available_sink: callback(envelope_hex: str), called from a channel reader thread
|
||||
with an encoded ENVELOPE EVENT DOWNLOAD (data available). The caller forwards it
|
||||
to the card with the ENVELOPE command, the card then FETCHes RECEIVE DATA.
|
||||
None: the event is only logged (card free / test mode).
|
||||
sms_sink: callback(pdu), called with the SMPP deliver_sm of a SEND SHORT MESSAGE
|
||||
the card issued; pySim-smpp2sim.py hands it to its SMPP server.
|
||||
None: the SMS is logged and dropped.
|
||||
"""
|
||||
def __init__(self, data_available_sink=None, sms_sink=None):
|
||||
self.data_available_sink = data_available_sink
|
||||
self.sms_sink = sms_sink
|
||||
self.channels = ProactChannels()
|
||||
self.channels = ProactChannels(on_data_available=self._on_channel_data_available)
|
||||
|
||||
@staticmethod
|
||||
def _find_first_element_of_type(instlist, cls):
|
||||
@@ -93,8 +226,82 @@ class Proact(ProactiveHandler):
|
||||
return i
|
||||
return None
|
||||
|
||||
"""Call-back which the pySim transport core calls whenever it receives a
|
||||
proactive command from the SIM."""
|
||||
@staticmethod
|
||||
def _channel_nr_from_dev_ids(dev_id_ie: DeviceIdentities) -> int:
|
||||
"""Maps id like channel_1 -> channel number.
|
||||
TS 102 223 Section 8.7 says low nibble is channel number,
|
||||
channel-N = 0x21..0x27"""
|
||||
dest = dev_id_ie.decoded['dest_dev_id']
|
||||
return DeviceIdentities.DEV_IDS.inverse[dest] & 0x0f
|
||||
|
||||
def _channel_for(self, dev_id_ie: DeviceIdentities):
|
||||
"""Resolve the ProactChannel addressed by a command dev id, or None"""
|
||||
return self.channels.channels.get(self._channel_nr_from_dev_ids(dev_id_ie), None)
|
||||
|
||||
@staticmethod
|
||||
def _channel_status(chan_nr: int, established: bool = True) -> str:
|
||||
"""TS 102 223 Section 8.56 channel status value for the
|
||||
default/network bearer:
|
||||
- byte 3 low 3 bits = channel id
|
||||
- bit 8 = link established
|
||||
- byte 4 = 00 no further info"""
|
||||
b3 = (0x80 if established else 0x00) | (chan_nr & 0x07)
|
||||
return '%02x00' % b3
|
||||
|
||||
def _bip_response_head(self, pcmd: ProactiveCommand,
|
||||
general_result: str = 'performed_successfully',
|
||||
additional_information: str = ''):
|
||||
"""CommandDetails / DeviceIdentities / Result head part of a BIP TERMINAL
|
||||
RESPONSE. Built on prepare_response() but with two changes:
|
||||
|
||||
- Device identities forced source=terminal, dest=UICC.
|
||||
TS 102 223 6.8.2 mandates for every TERMINAL RESPONSE
|
||||
prepare_response() inverts the commands device id, which is
|
||||
right for a uicc->terminal command but would yield a wrong
|
||||
channel_N->UICC for the channel addressed BIP commands.
|
||||
|
||||
- Result is recreated for non success cases. prepare_response()
|
||||
hard codes empty "additional information", but for enum results
|
||||
like BIP error -> AddlInfoBip the empty value cannot be encoded at
|
||||
all, so we always ask prepare_response() for a success Result
|
||||
and swap for a properly encoded one here."""
|
||||
head = self.prepare_response(pcmd, 'performed_successfully')
|
||||
for i, ie in enumerate(head):
|
||||
if isinstance(ie, DeviceIdentities):
|
||||
head[i] = DeviceIdentities(decoded={'source_dev_id': 'terminal',
|
||||
'dest_dev_id': 'uicc'})
|
||||
elif isinstance(ie, Result) and general_result != 'performed_successfully':
|
||||
res = Result()
|
||||
res.from_dict({'result': {'general_result': general_result,
|
||||
'additional_information': additional_information}})
|
||||
head[i] = res
|
||||
return head
|
||||
|
||||
def _build_data_available_envelope(self, chan: ProactChannel) -> bytes:
|
||||
"""TS 102 223 7.5.10.2 ENVELOPE EVENT DOWNLOAD
|
||||
Event list, Device id terminal->UICC, Channel status,
|
||||
Channel data length (bytes available or FF for > 255)."""
|
||||
avail = min(chan.available_rx(), 0xff)
|
||||
ed = EventDownload(children=[
|
||||
EventList(decoded=['data_available']),
|
||||
DeviceIdentities(decoded={'source_dev_id': 'terminal', 'dest_dev_id': 'uicc'}),
|
||||
ChannelStatus(decoded=self._channel_status(chan.chan_nr)),
|
||||
ChannelDataLength(decoded=avail),
|
||||
])
|
||||
return ed.to_tlv()
|
||||
|
||||
def _on_channel_data_available(self, chan: ProactChannel):
|
||||
"""rx reader thread hook: socket data arrived while the channel buffer
|
||||
was empty. card uses ENVELOPE EVENT DOWNLOAD + responds by FETCHing RECEIVE DATA
|
||||
proactive command. Card free only builds and logs"""
|
||||
envelope_hex = b2h(self._build_data_available_envelope(chan))
|
||||
logger.info("channel %u: %u byte(s) available -> ENVELOPE(Data available) %s",
|
||||
chan.chan_nr, chan.available_rx(), envelope_hex)
|
||||
if self.data_available_sink:
|
||||
self.data_available_sink(envelope_hex)
|
||||
|
||||
# handle_*: called by the transport with the decoded proactive command, the returned IE
|
||||
# list becomes the TERMINAL RESPONSE.
|
||||
def handle_SendShortMessage(self, pcmd: ProactiveCommand):
|
||||
# {'smspp_download': [{'device_identities': {'source_dev_id': 'network',
|
||||
# 'dest_dev_id': 'uicc'}},
|
||||
@@ -104,7 +311,8 @@ class Proact(ProactiveHandler):
|
||||
# 'call_number': '79'}},
|
||||
# {'sms_tpdu': {'tpdu': '40048111227ff6407070611535004d02700000481516011212000001fe4c0943aea42e45021c078ae06c66afc09303608874b72f58bacadb0dcf665c29349c799fbb522e61709c9baf1890015e8e8e196e36153106c8b92f95153774'}}
|
||||
# ]}
|
||||
"""Card requests sending a SMS. We need to pass it on to the ESME via SMPP."""
|
||||
"""SEND SHORT MESSAGE: hand the MO-SMS to sms_sink, answer with success so the card
|
||||
continues with the next part of a multi part response."""
|
||||
logger.info("SendShortMessage")
|
||||
logger.info(pcmd)
|
||||
# Relevant parts in pcmd: Address, SMS_TPDU
|
||||
@@ -126,7 +334,8 @@ class Proact(ProactiveHandler):
|
||||
return self.prepare_response(pcmd)
|
||||
|
||||
def handle_OpenChannel(self, pcmd: ProactiveCommand):
|
||||
"""Card requests opening a new channel via a UDP/TCP socket."""
|
||||
"""OPEN CHANNEL: connect a TCP socket to the given address and port, allocate a
|
||||
channel number and report it in the Channel status of the response."""
|
||||
# {'open_channel': [{'command_details': {'command_number': 1,
|
||||
# 'type_of_command': 'open_channel',
|
||||
# 'command_qualifier': 3}},
|
||||
@@ -150,16 +359,22 @@ class Proact(ProactiveHandler):
|
||||
raise ValueError('Unsupported protocol_type')
|
||||
if other_addr_ie.decoded.get('type_of_address', None) != 'ipv4':
|
||||
raise ValueError('Unsupported type_of_address')
|
||||
ipv4_bytes = h2b(other_addr_ie.decoded['address'])
|
||||
ipv4_str = '%u.%u.%u.%u' % (ipv4_bytes[0], ipv4_bytes[1], ipv4_bytes[2], ipv4_bytes[3])
|
||||
addr_bytes = h2b(other_addr_ie.decoded['address']) if isinstance(
|
||||
other_addr_ie.decoded['address'], str) else other_addr_ie.decoded['address']
|
||||
ipv4_str = '%u.%u.%u.%u' % (addr_bytes[0], addr_bytes[1], addr_bytes[2], addr_bytes[3])
|
||||
port_nr = transp_lvl_ie.decoded['port_number']
|
||||
print("%s:%u" % (ipv4_str, port_nr))
|
||||
logger.info("OpenChannel: connecting to %s:%u", ipv4_str, port_nr)
|
||||
channel = self.channels.channel_create()
|
||||
channel.ep = endpoints.TCP4ClientEndpoint(reactor, ipv4_str, port_nr)
|
||||
channel.prot = TcpProtocol()
|
||||
d = endpoints.connectProtocol(channel.ep, channel.prot)
|
||||
# FIXME: why is this never called despite the client showing the inbound connection?
|
||||
d.addCallback(tcp_connected_callback)
|
||||
# yes, blocking connect()
|
||||
try:
|
||||
channel.connect(ipv4_str, port_nr)
|
||||
except OSError as e:
|
||||
logger.warning("OpenChannel: connect to %s:%u failed: %s", ipv4_str, port_nr, e)
|
||||
self.channels.channel_delete(channel.chan_nr)
|
||||
return self._bip_response_head(pcmd, 'bearer_independent_protocol_error',
|
||||
'channel_closed') + [
|
||||
ChannelStatus(decoded=self._channel_status(channel.chan_nr, established=False)),
|
||||
bearer_desc_ie, buffer_size_ie]
|
||||
|
||||
# Terminal Response example: [
|
||||
# {'command_details': {'command_number': 1,
|
||||
@@ -171,15 +386,26 @@ class Proact(ProactiveHandler):
|
||||
# {'bearer_description': {'bearer_type': 'default', 'bearer_parameters': ''}},
|
||||
# {'buffer_size': 1024}
|
||||
# ]
|
||||
return self.prepare_response(pcmd) + [ChannelStatus(decoded='8100'), bearer_desc_ie, buffer_size_ie]
|
||||
return self._bip_response_head(pcmd) + [
|
||||
ChannelStatus(decoded=self._channel_status(channel.chan_nr)),
|
||||
bearer_desc_ie, buffer_size_ie]
|
||||
|
||||
def handle_CloseChannel(self, pcmd: ProactiveCommand):
|
||||
"""Close a channel."""
|
||||
"""CLOSE CHANNEL: close the socket of the addressed channel and free its number."""
|
||||
logger.info("CloseChannel")
|
||||
logger.info(pcmd)
|
||||
dev_id_ie = Proact._find_first_element_of_type(pcmd.children, DeviceIdentities)
|
||||
chan = self._channel_for(dev_id_ie)
|
||||
if chan is None:
|
||||
# channel closed / invalid
|
||||
return self._bip_response_head(pcmd, 'bearer_independent_protocol_error',
|
||||
'channel_id_not_valid')
|
||||
chan.close()
|
||||
return self._bip_response_head(pcmd)
|
||||
|
||||
def handle_ReceiveData(self, pcmd: ProactiveCommand):
|
||||
"""Receive/read data from the socket."""
|
||||
"""RECEIVE DATA: the card fetches up to Channel data length bytes from the Rx buffer
|
||||
of the addressed channel, the response also carries how many bytes remain."""
|
||||
# {'receive_data': [{'command_details': {'command_number': 1,
|
||||
# 'type_of_command': 'receive_data',
|
||||
# 'command_qualifier': 0}},
|
||||
@@ -189,6 +415,21 @@ class Proact(ProactiveHandler):
|
||||
# ]}
|
||||
logger.info("ReceiveData")
|
||||
logger.info(pcmd)
|
||||
dev_id_ie = Proact._find_first_element_of_type(pcmd.children, DeviceIdentities)
|
||||
req_len_ie = Proact._find_first_element_of_type(pcmd.children, ChannelDataLength)
|
||||
chan = self._channel_for(dev_id_ie)
|
||||
if chan is None:
|
||||
return self._bip_response_head(pcmd, 'bearer_independent_protocol_error',
|
||||
'channel_id_not_valid')
|
||||
# TS 102 223 8.54: RECEIVE DATA contains the requested count the card wants
|
||||
requested = req_len_ie.decoded if req_len_ie is not None else chan.available_rx()
|
||||
data, remaining = chan.take_rx(requested)
|
||||
# TS 102 223 6.4.29:
|
||||
# - return data available in the Rx buffer + num bytes still remaining (FF if > 255)
|
||||
# - if fewer than requested available terminal must NOT wait, report and returns what we have
|
||||
general_result = 'performed_successfully'
|
||||
if len(data) < requested:
|
||||
general_result = 'performed_with_missing_information'
|
||||
# Terminal Response example: [
|
||||
# {'command_details': {'command_number': 1,
|
||||
# 'type_of_command': 'receive_data',
|
||||
@@ -198,10 +439,13 @@ class Proact(ProactiveHandler):
|
||||
# {'channel_data': '16030100040e000000'},
|
||||
# {'channel_data_length': 0}
|
||||
# ]
|
||||
return self.prepare_response(pcmd) + []
|
||||
return self._bip_response_head(pcmd, general_result) + [
|
||||
ChannelData(decoded=b2h(data)),
|
||||
ChannelDataLength(decoded=min(remaining, 0xff))]
|
||||
|
||||
def handle_SendData(self, pcmd: ProactiveCommand):
|
||||
"""Send/write data received from the SIM to the socket."""
|
||||
"""SEND DATA: write the Channel data of the command to the socket of the addressed
|
||||
channel."""
|
||||
# {'send_data': [{'command_details': {'command_number': 1,
|
||||
# 'type_of_command': 'send_data',
|
||||
# 'command_qualifier': 1}},
|
||||
@@ -213,10 +457,18 @@ class Proact(ProactiveHandler):
|
||||
logger.info(pcmd)
|
||||
dev_id_ie = Proact._find_first_element_of_type(pcmd.children, DeviceIdentities)
|
||||
chan_data_ie = Proact._find_first_element_of_type(pcmd.children, ChannelData)
|
||||
chan_str = dev_id_ie.decoded['dest_dev_id']
|
||||
chan_nr = 1 # FIXME
|
||||
chan = self.channels.channels.get(chan_nr, None)
|
||||
# FIXME chan.prot.transport.write(h2b(chan_data_ie.decoded))
|
||||
chan = self._channel_for(dev_id_ie)
|
||||
if chan is None:
|
||||
return self._bip_response_head(pcmd, 'bearer_independent_protocol_error',
|
||||
'channel_id_not_valid')
|
||||
# lets accept hexstrings as well
|
||||
payload = chan_data_ie.decoded
|
||||
if isinstance(payload, str):
|
||||
payload = h2b(payload)
|
||||
# command_qualifier bit 1 selects 'send immediately' / Tx-buffer store and forward
|
||||
# For TCP stream all we have is a socket and TCP takes care of segmentation,
|
||||
# so just send.
|
||||
chan.send(payload)
|
||||
# Terminal Response example: [
|
||||
# {'command_details': {'command_number': 1,
|
||||
# 'type_of_command': 'send_data',
|
||||
@@ -225,9 +477,11 @@ class Proact(ProactiveHandler):
|
||||
# {'result': {'general_result': 'performed_successfully', 'additional_information': ''}},
|
||||
# {'channel_data_length': 255}
|
||||
# ]
|
||||
return self.prepare_response(pcmd) + [ChannelDataLength(decoded=255)]
|
||||
# TS 102 223 6.4.30 / 8.54 Channel data length = free space tx buf; FF == > 255 available
|
||||
return self._bip_response_head(pcmd) + [ChannelDataLength(decoded=255)]
|
||||
|
||||
def handle_SetUpEventList(self, pcmd: ProactiveCommand):
|
||||
"""SET UP EVENT LIST: acknowledged, data available and channel status are always on."""
|
||||
# {'set_up_event_list': [{'command_details': {'command_number': 1,
|
||||
# 'type_of_command': 'set_up_event_list',
|
||||
# 'command_qualifier': 0}},
|
||||
|
||||
Reference in New Issue
Block a user