utils.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. # SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: Apache-2.0
  3. import argparse
  4. import binascii
  5. import os
  6. import uuid
  7. from datetime import datetime
  8. from typing import List, Optional, Tuple
  9. from construct import BitsInteger, BitStruct, Int16ul
  10. FAT12_MAX_CLUSTERS: int = 4085
  11. FAT16_MAX_CLUSTERS: int = 65525
  12. PAD_CHAR: int = 0x20
  13. FAT12: int = 12
  14. FAT16: int = 16
  15. FAT32: int = 32
  16. FULL_BYTE: bytes = b'\xff'
  17. EMPTY_BYTE: bytes = b'\x00'
  18. # redundant
  19. BYTES_PER_DIRECTORY_ENTRY: int = 32
  20. UINT32_MAX: int = (1 << 32) - 1
  21. MAX_NAME_SIZE: int = 8
  22. MAX_EXT_SIZE: int = 3
  23. DATETIME = Tuple[int, int, int]
  24. FATFS_INCEPTION_YEAR: int = 1980
  25. FATFS_INCEPTION: datetime = datetime(FATFS_INCEPTION_YEAR, 1, 1, 0, 0, 0, 0)
  26. FATFS_MAX_HOURS = 24
  27. FATFS_MAX_MINUTES = 60
  28. FATFS_MAX_SECONDS = 60
  29. FATFS_MAX_DAYS = 31
  30. FATFS_MAX_MONTHS = 12
  31. FATFS_MAX_YEARS = 127
  32. FATFS_SECONDS_GRANULARITY: int = 2
  33. # long names are encoded to two bytes in utf-16
  34. LONG_NAMES_ENCODING: str = 'utf-16'
  35. SHORT_NAMES_ENCODING: str = 'utf-8'
  36. ALLOWED_SECTOR_SIZES: List[int] = [512, 1024, 2048, 4096]
  37. ALLOWED_SECTORS_PER_CLUSTER: List[int] = [1, 2, 4, 8, 16, 32, 64, 128]
  38. def crc32(input_values: List[int], crc: int) -> int:
  39. """
  40. Name Polynomial Reversed? Init-value XOR-out
  41. crc32 0x104C11DB7 True 4294967295 (UINT32_MAX) 0xFFFFFFFF
  42. """
  43. return binascii.crc32(bytearray(input_values), crc)
  44. def number_of_clusters(number_of_sectors: int, sectors_per_cluster: int) -> int:
  45. return number_of_sectors // sectors_per_cluster
  46. def get_non_data_sectors_cnt(reserved_sectors_cnt: int, sectors_per_fat_cnt: int, root_dir_sectors_cnt: int) -> int:
  47. return reserved_sectors_cnt + sectors_per_fat_cnt + root_dir_sectors_cnt
  48. def get_fatfs_type(clusters_count: int) -> int:
  49. if clusters_count < FAT12_MAX_CLUSTERS:
  50. return FAT12
  51. if clusters_count <= FAT16_MAX_CLUSTERS:
  52. return FAT16
  53. return FAT32
  54. def required_clusters_count(cluster_size: int, content: bytes) -> int:
  55. # compute number of required clusters for file text
  56. return (len(content) + cluster_size - 1) // cluster_size
  57. def generate_4bytes_random() -> int:
  58. return uuid.uuid4().int & 0xFFFFFFFF
  59. def pad_string(content: str, size: Optional[int] = None, pad: int = PAD_CHAR) -> str:
  60. # cut string if longer and fill with pad character if shorter than size
  61. return content.ljust(size or len(content), chr(pad))[:size]
  62. def right_strip_string(content: str, pad: int = PAD_CHAR) -> str:
  63. return content.rstrip(chr(pad))
  64. def build_lfn_short_entry_name(name: str, extension: str, order: int) -> str:
  65. return '{}{}'.format(pad_string(content=name[:MAX_NAME_SIZE - 2] + '~' + chr(order), size=MAX_NAME_SIZE),
  66. pad_string(extension[:MAX_EXT_SIZE], size=MAX_EXT_SIZE))
  67. def lfn_checksum(short_entry_name: str) -> int:
  68. """
  69. Function defined by FAT specification. Computes checksum out of name in the short file name entry.
  70. """
  71. checksum_result = 0
  72. for i in range(MAX_NAME_SIZE + MAX_EXT_SIZE):
  73. # operation is a right rotation on 8 bits (Python equivalent for unsigned char in C)
  74. checksum_result = (0x80 if checksum_result & 1 else 0x00) + (checksum_result >> 1) + ord(short_entry_name[i])
  75. checksum_result &= 0xff
  76. return checksum_result
  77. def convert_to_utf16_and_pad(content: str,
  78. expected_size: int,
  79. pad: bytes = FULL_BYTE,
  80. terminator: bytes = b'\x00\x00') -> bytes:
  81. # we need to get rid of the Byte order mark 0xfeff or 0xfffe, fatfs does not use it
  82. bom_utf16: bytes = b'\xfe\xff'
  83. encoded_content_utf16: bytes = content.encode(LONG_NAMES_ENCODING)[len(bom_utf16):]
  84. terminated_encoded_content_utf16: bytes = (encoded_content_utf16 + terminator) if (2 * expected_size > len(
  85. encoded_content_utf16) > 0) else encoded_content_utf16
  86. return terminated_encoded_content_utf16.ljust(2 * expected_size, pad)
  87. def split_to_name_and_extension(full_name: str) -> Tuple[str, str]:
  88. name, extension = os.path.splitext(full_name)
  89. return name, extension.replace('.', '')
  90. def is_valid_fatfs_name(string: str) -> bool:
  91. return string == string.upper()
  92. def split_by_half_byte_12_bit_little_endian(value: int) -> Tuple[int, int, int]:
  93. value_as_bytes: bytes = Int16ul.build(value)
  94. return value_as_bytes[0] & 0x0f, value_as_bytes[0] >> 4, value_as_bytes[1] & 0x0f
  95. def merge_by_half_byte_12_bit_little_endian(v1: int, v2: int, v3: int) -> int:
  96. return v1 | v2 << 4 | v3 << 8
  97. def build_byte(first_half: int, second_half: int) -> int:
  98. return (first_half << 4) | second_half
  99. def split_content_into_sectors(content: bytes, sector_size: int) -> List[bytes]:
  100. result = []
  101. clusters_cnt: int = required_clusters_count(cluster_size=sector_size, content=content)
  102. for i in range(clusters_cnt):
  103. result.append(content[sector_size * i:(i + 1) * sector_size])
  104. return result
  105. def get_args_for_partition_generator(desc: str) -> argparse.Namespace:
  106. parser: argparse.ArgumentParser = argparse.ArgumentParser(description=desc)
  107. parser.add_argument('input_directory',
  108. help='Path to the directory that will be encoded into fatfs image')
  109. parser.add_argument('--output_file',
  110. default='fatfs_image.img',
  111. help='Filename of the generated fatfs image')
  112. parser.add_argument('--partition_size',
  113. default=FATDefaults.SIZE,
  114. help='Size of the partition in bytes')
  115. parser.add_argument('--sector_size',
  116. default=FATDefaults.SECTOR_SIZE,
  117. type=int,
  118. choices=ALLOWED_SECTOR_SIZES,
  119. help='Size of the partition in bytes')
  120. parser.add_argument('--sectors_per_cluster',
  121. default=1,
  122. type=int,
  123. choices=ALLOWED_SECTORS_PER_CLUSTER,
  124. help='Number of sectors per cluster')
  125. parser.add_argument('--root_entry_count',
  126. default=FATDefaults.ROOT_ENTRIES_COUNT,
  127. help='Number of entries in the root directory')
  128. parser.add_argument('--long_name_support',
  129. action='store_true',
  130. help='Set flag to enable long names support.')
  131. parser.add_argument('--use_default_datetime',
  132. action='store_true',
  133. help='For test purposes. If the flag is set the files are created with '
  134. 'the default timestamp that is the 1st of January 1980')
  135. parser.add_argument('--fat_type',
  136. default=0,
  137. type=int,
  138. choices=[12, 16, 0],
  139. help="""
  140. Type of fat. Select 12 for fat12, 16 for fat16. Don't set, or set to 0 for automatic
  141. calculation using cluster size and partition size.
  142. """)
  143. args = parser.parse_args()
  144. if args.fat_type == 0:
  145. args.fat_type = None
  146. args.partition_size = int(str(args.partition_size), 0)
  147. if not os.path.isdir(args.input_directory):
  148. raise NotADirectoryError(f'The target directory `{args.input_directory}` does not exist!')
  149. return args
  150. def read_filesystem(path: str) -> bytearray:
  151. with open(path, 'rb') as fs_file:
  152. return bytearray(fs_file.read())
  153. DATE_ENTRY = BitStruct(
  154. 'year' / BitsInteger(7),
  155. 'month' / BitsInteger(4),
  156. 'day' / BitsInteger(5))
  157. TIME_ENTRY = BitStruct(
  158. 'hour' / BitsInteger(5),
  159. 'minute' / BitsInteger(6),
  160. 'second' / BitsInteger(5),
  161. )
  162. def build_date_entry(year: int, mon: int, mday: int) -> int:
  163. """
  164. :param year: denotes year starting from 1980 (0 ~ 1980, 1 ~ 1981, etc), valid values are 1980 + 0..127 inclusive
  165. thus theoretically 1980 - 2107
  166. :param mon: denotes number of month of year in common order (1 ~ January, 2 ~ February, etc.),
  167. valid values: 1..12 inclusive
  168. :param mday: denotes number of day in month, valid values are 1..31 inclusive
  169. :returns: 16 bit integer number (7 bits for year, 4 bits for month and 5 bits for day of the month)
  170. """
  171. assert year in range(FATFS_INCEPTION_YEAR, FATFS_INCEPTION_YEAR + FATFS_MAX_YEARS)
  172. assert mon in range(1, FATFS_MAX_MONTHS + 1)
  173. assert mday in range(1, FATFS_MAX_DAYS + 1)
  174. return int.from_bytes(DATE_ENTRY.build(dict(year=year - FATFS_INCEPTION_YEAR, month=mon, day=mday)), 'big')
  175. def build_time_entry(hour: int, minute: int, sec: int) -> int:
  176. """
  177. :param hour: denotes number of hour, valid values are 0..23 inclusive
  178. :param minute: denotes minutes, valid range 0..59 inclusive
  179. :param sec: denotes seconds with granularity 2 seconds (e.g. 1 ~ 2, 29 ~ 58), valid range 0..29 inclusive
  180. :returns: 16 bit integer number (5 bits for hour, 6 bits for minute and 5 bits for second)
  181. """
  182. assert hour in range(FATFS_MAX_HOURS)
  183. assert minute in range(FATFS_MAX_MINUTES)
  184. assert sec in range(FATFS_MAX_SECONDS)
  185. return int.from_bytes(TIME_ENTRY.build(
  186. dict(hour=hour, minute=minute, second=sec // FATFS_SECONDS_GRANULARITY)),
  187. byteorder='big'
  188. )
  189. class FATDefaults:
  190. # FATFS defaults
  191. SIZE: int = 1024 * 1024
  192. RESERVED_SECTORS_COUNT: int = 1
  193. FAT_TABLES_COUNT: int = 1
  194. SECTORS_PER_CLUSTER: int = 1
  195. SECTOR_SIZE: int = 0x1000
  196. SECTORS_PER_FAT: int = 1
  197. HIDDEN_SECTORS: int = 0
  198. ENTRY_SIZE: int = 32
  199. NUM_HEADS: int = 0xff
  200. OEM_NAME: str = 'MSDOS5.0'
  201. SEC_PER_TRACK: int = 0x3f
  202. VOLUME_LABEL: str = 'Espressif'
  203. FILE_SYS_TYPE: str = 'FAT'
  204. ROOT_ENTRIES_COUNT: int = 512 # number of entries in the root directory
  205. MEDIA_TYPE: int = 0xf8
  206. SIGNATURE_WORD: bytes = b'\x55\xAA'
  207. # wear levelling defaults
  208. VERSION: int = 2
  209. TEMP_BUFFER_SIZE: int = 32
  210. UPDATE_RATE: int = 16