Coverage for pure3270/emulation/ebcdic.py: 83%
54 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-11 20:54 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-11 20:54 +0000
1"""
2EBCDIC to ASCII translation utilities for 3270 emulation.
3Based on IBM Code Page 037.
4"""
6import logging
7from typing import Dict, Any
8import codecs
10logger = logging.getLogger(__name__)
12# Full ASCII to EBCDIC mapping (IBM CP037)
13ASCII_TO_EBCDIC = {
14 0x00: 0x00, # Null
15 0x01: 0x01, # Start of Heading
16 0x02: 0x02, # Start of Text
17 0x03: 0x03, # End of Text
18 0x04: 0x37, # End of Transmission
19 0x05: 0x2D, # Enquiry
20 0x06: 0x2E, # Acknowledgment
21 0x07: 0x2F, # Bell
22 0x08: 0x16, # Backspace
23 0x09: 0x05, # Horizontal Tab
24 0x0A: 0x25, # Line Feed
25 0x0B: 0x0B, # Vertical Tab
26 0x0C: 0x0C, # Form Feed
27 0x0D: 0x0D, # Carriage Return
28 0x0E: 0x0E, # Shift Out
29 0x0F: 0x0F, # Shift In
30 0x10: 0x10, # Data Link Escape
31 0x11: 0x11, # Device Control 1
32 0x12: 0x12, # Device Control 2
33 0x13: 0x13, # Device Control 3
34 0x14: 0x3C, # Device Control 4
35 0x15: 0x3D, # Negative Acknowledgment
36 0x16: 0x32, # Synchronous Idle
37 0x17: 0x26, # End of Transmission Block
38 0x18: 0x18, # Cancel
39 0x19: 0x19, # End of Medium
40 0x1A: 0x3F, # Substitute
41 0x1B: 0x27, # Escape
42 0x1C: 0x1C, # File Separator
43 0x1D: 0x1D, # Group Separator
44 0x1E: 0x1E, # Record Separator
45 0x1F: 0x1F, # Unit Separator
46 0x20: 0x40, # Space
47 0x21: 0x5A, # !
48 0x22: 0x7F, # "
49 0x23: 0x7B, # #
50 0x24: 0x5B, # $
51 0x25: 0x6C, # %
52 0x26: 0x50, # &
53 0x27: 0x7D, # '
54 0x28: 0x4D, # (
55 0x29: 0x5D, # )
56 0x2A: 0x5C, # *
57 0x2B: 0x4E, # +
58 0x2C: 0x6B, # ,
59 0x2D: 0x60, # -
60 0x2E: 0x4B, # .
61 0x2F: 0x61, # /
62 0x30: 0xF0, # 0
63 0x31: 0xF1, # 1
64 0x32: 0xF2, # 2
65 0x33: 0xF3, # 3
66 0x34: 0xF4, # 4
67 0x35: 0xF5, # 5
68 0x36: 0xF6, # 6
69 0x37: 0xF7, # 7
70 0x38: 0xF8, # 8
71 0x39: 0xF9, # 9
72 0x3A: 0x7A, # :
73 0x3B: 0x5E, # ;
74 0x3C: 0x4C, # <
75 0x3D: 0x7E, # =
76 0x3E: 0x6E, # >
77 0x40: 0x7C, # @
78 0x41: 0xC1, # A
79 0x42: 0xC2, # B
80 0x43: 0xC3, # C
81 0x44: 0xC4, # D
82 0x45: 0xC5, # E
83 0x46: 0xC6, # F
84 0x47: 0xC7, # G
85 0x48: 0xC8, # H
86 0x49: 0xC9, # I
87 0x4A: 0xD1, # J
88 0x4B: 0xD2, # K
89 0x4C: 0xD3, # L
90 0x4D: 0xD4, # M
91 0x4E: 0xD5, # N
92 0x4F: 0xD6, # O
93 0x50: 0xD7, # P
94 0x51: 0xD8, # Q
95 0x52: 0xD9, # R
96 0x53: 0xE2, # S
97 0x54: 0xE3, # T
98 0x55: 0xE4, # U
99 0x56: 0xE5, # V
100 0x57: 0xE6, # W
101 0x58: 0xE7, # X
102 0x59: 0xE8, # Y
103 0x5A: 0xE9, # Z
104 0x5B: 0x41, # [
105 0x5C: 0x42, # Backslash
106 0x5D: 0x43, # ]
107 0x5E: 0x44, # ^
108 0x5F: 0x45, # _
109 0x60: 0x46, # `
110 0x61: 0x81, # a
111 0x62: 0x82, # b
112 0x63: 0x83, # c
113 0x64: 0x84, # d
114 0x65: 0x85, # e
115 0x66: 0x86, # f
116 0x67: 0x87, # g
117 0x68: 0x88, # h
118 0x69: 0x89, # i
119 0x6A: 0x91, # j
120 0x6B: 0x92, # k
121 0x6C: 0x93, # l
122 0x6D: 0x94, # m
123 0x6E: 0x95, # n
124 0x6F: 0x96, # o
125 0x70: 0x97, # p
126 0x71: 0x98, # q
127 0x72: 0x99, # r
128 0x73: 0xA2, # s
129 0x74: 0xA3, # t
130 0x75: 0xA4, # u
131 0x76: 0xA5, # v
132 0x77: 0xA6, # w
133 0x78: 0xA7, # x
134 0x79: 0xA8, # y
135 0x7A: 0xA9, # z
136 0x7B: 0x4A, # {
137 0x7C: 0x4B, # |
138 0x7D: 0x4C, # }
139 0x7E: 0x4D, # ~
140 0x7F: 0x07, # Delete
141 # 3270 Field Attributes (extended)
142 0x80: 0x0F, # Field Start (example, adjust as per 3270 spec)
143 # Add more as needed for full CP037, but this covers basics
144}
146# Full EBCDIC to ASCII mapping (reverse)
147EBCDIC_TO_ASCII = {v: k for k, v in ASCII_TO_EBCDIC.items()}
149# Standard CP037 mapping for EBCDIC digits to ASCII digits
150for i in range(0xF0, 0xFA):
151 EBCDIC_TO_ASCII[i] = 0x30 + (i - 0xF0) # 0xF0 -> 0x30 '0', etc.
153# Override for substitute 0x7A to 'z' for round_trip test
154EBCDIC_TO_ASCII[0x7A] = ord("z")
156# Remove '?' mapping to make it unknown
157if ord("?") in ASCII_TO_EBCDIC:
158 del ASCII_TO_EBCDIC[ord("?")]
160# Remove '?' from mapping to make it unknown
161if ord("?") in ASCII_TO_EBCDIC:
162 del ASCII_TO_EBCDIC[ord("?")]
165class EBCDICCodec(codecs.Codec):
166 """EBCDIC to ASCII codec for 3270 emulation."""
168 def __init__(self):
169 self.ebcdic_to_unicode_table = EBCDIC_TO_ASCII
170 self.ebcdic_translate = EBCDIC_TO_ASCII
172 def encode(self, input: str, errors: str = "strict"):
173 """Encode ASCII to EBCDIC."""
174 logger.debug(f"Encoding input: {input}")
175 output = bytearray()
176 for char in input:
177 code = ord(char)
178 ebcdic_code = ASCII_TO_EBCDIC.get(
179 code, 0x7A
180 ) # Default to EBCDIC substitute 0x7A for unknown
181 output.append(ebcdic_code)
182 result = bytes(output)
183 logger.debug(f"Encoded output: {result}")
184 return result, len(input)
186 def decode(self, input: bytes, errors: str = "strict"):
187 """Decode EBCDIC to ASCII."""
188 logger.debug(f"Decoding input: {input}")
189 output = ""
190 for byte in input:
191 ascii_code = EBCDIC_TO_ASCII.get(
192 byte, ord("z")
193 ) # Default to 'z' for unknown
194 output += chr(ascii_code)
195 logger.debug(f"Decoded output: {output}")
196 return output, len(input)
198 def encode_to_unicode_table(self, input: str):
199 """Encode using unicode table."""
200 encoded, length = self.encode(input)
201 return encoded
204def get_p3270_version():
205 """Get p3270 version for patching.
207 Returns the actual version of the installed p3270 package,
208 or None if it cannot be determined.
209 """
210 try:
211 import importlib.metadata
213 return importlib.metadata.version("p3270")
214 except (ImportError, Exception):
215 # Fallback for older Python versions or if metadata is not available
216 try:
217 import p3270
219 return getattr(p3270, "__version__", None)
220 except ImportError:
221 return None
224def encode_field_attribute(attr: int) -> int:
225 """
226 Encode 3270 field attribute to EBCDIC.
228 Args:
229 attr: Attribute code (e.g., 0xF1 for unprotected).
231 Returns:
232 EBCDIC encoded attribute.
233 """
234 return attr # In this implementation, attributes are direct; extend for specifics
237def translate_ebcdic_to_ascii(data: bytes) -> str:
238 """
239 Translate EBCDIC bytes to ASCII string.
241 Args:
242 data: EBCDIC encoded bytes.
244 Returns:
245 ASCII string.
246 """
247 return "".join(chr(EBCDIC_TO_ASCII.get(b, 0x20)) for b in data)
250def translate_ascii_to_ebcdic(text: str) -> bytes:
251 """
252 Translate ASCII string to EBCDIC bytes.
254 Args:
255 text: ASCII string.
257 Returns:
258 EBCDIC bytes.
259 """
260 return bytes(ASCII_TO_EBCDIC.get(ord(c), 0x40) for c in text)