1# Copyright 2023 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15from typing import List, Union 16 17from bumble import core 18 19 20class AtParsingError(core.InvalidPacketError): 21 """Error raised when parsing AT commands fails.""" 22 23 24def tokenize_parameters(buffer: bytes) -> List[bytes]: 25 """Split input parameters into tokens. 26 Removes space characters outside of double quote blocks: 27 T-rec-V-25 - 5.2.1 Command line general format: "Space characters (IA5 2/0) 28 are ignored [..], unless they are embedded in numeric or string constants" 29 Raises AtParsingError in case of invalid input string.""" 30 31 tokens = [] 32 in_quotes = False 33 token = bytearray() 34 for b in buffer: 35 char = bytearray([b]) 36 37 if in_quotes: 38 token.extend(char) 39 if char == b'\"': 40 in_quotes = False 41 tokens.append(token[1:-1]) 42 token = bytearray() 43 else: 44 if char == b' ': 45 pass 46 elif char == b',' or char == b')': 47 tokens.append(token) 48 tokens.append(char) 49 token = bytearray() 50 elif char == b'(': 51 if len(token) > 0: 52 raise AtParsingError("open_paren following regular character") 53 tokens.append(char) 54 elif char == b'"': 55 if len(token) > 0: 56 raise AtParsingError("quote following regular character") 57 in_quotes = True 58 token.extend(char) 59 else: 60 token.extend(char) 61 62 tokens.append(token) 63 return [bytes(token) for token in tokens if len(token) > 0] 64 65 66def parse_parameters(buffer: bytes) -> List[Union[bytes, list]]: 67 """Parse the parameters using the comma and parenthesis separators. 68 Raises AtParsingError in case of invalid input string.""" 69 70 tokens = tokenize_parameters(buffer) 71 accumulator: List[list] = [[]] 72 current: Union[bytes, list] = bytes() 73 74 for token in tokens: 75 if token == b',': 76 accumulator[-1].append(current) 77 current = bytes() 78 elif token == b'(': 79 accumulator.append([]) 80 elif token == b')': 81 if len(accumulator) < 2: 82 raise AtParsingError("close_paren without matching open_paren") 83 accumulator[-1].append(current) 84 current = accumulator.pop() 85 else: 86 current = token 87 88 accumulator[-1].append(current) 89 if len(accumulator) > 1: 90 raise AtParsingError("missing close_paren") 91 return accumulator[0] 92