Skip to content

Commit 6aa7034

Browse files
upy-fs-builder: Replicate MicroPython behaviour with extra 0xFF byte
When the last chunk is used up to the last byte the micro:bit MicroPython file system will also use the next chunk and leave it empty (the double link list pointers and end file offset will still be calculated like there is an extra 0xFF at the end of data). This commit replicates that behaviour by adding an extra 0xFF byte at the end of the data.
1 parent 425a234 commit 6aa7034

File tree

2 files changed

+146
-27
lines changed

2 files changed

+146
-27
lines changed

src/__tests__/micropython-fs-builder.spec.ts

Lines changed: 140 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1+
/**
2+
* All the hex file strings generated below have been created by transferring
3+
* the files to a micro:bit running MicroPython v1.0.1 using Mu.
4+
* Because the filesystem limits depend on the MicroPython version, these will
5+
* only work if combined with v1.0.1.
6+
*/
17
import * as fs from 'fs';
28

39
import MemoryMap from 'nrf-intel-hex';
410

5-
import { strToBytes } from '../common';
11+
import { bytesToStr, strToBytes } from '../common';
612
import {
713
addIntelHexFile,
814
addIntelHexFiles,
@@ -177,6 +183,126 @@ describe('Writing files to the filesystem.', () => {
177183
expect(file1data).toEqual(files[1].bytes());
178184
});
179185

186+
// A chunk using up the last byte will also use the next and leave it empty
187+
const fullChunkPlus = {
188+
fileName: 'one_chunk_plus.py',
189+
fileStr:
190+
'a = """abcdefghijklmnopqrstuvwxyz\n' +
191+
'abcdefghijklmnopqrstuvwxyz\n' +
192+
'abcdefghijklmnopqrstuvwxyz\n' +
193+
'abcdefghijklmno"""\n',
194+
hex:
195+
':020000040003F7\n' +
196+
':108C0000FE00116F6E655F6368756E6B5F706C75EB\n' +
197+
':108C1000732E707961203D20222222616263646597\n' +
198+
':108C2000666768696A6B6C6D6E6F7071727374756C\n' +
199+
':108C3000767778797A0A6162636465666768696ADB\n' +
200+
':108C40006B6C6D6E6F707172737475767778797AFC\n' +
201+
':108C50000A6162636465666768696A6B6C6D6E6FF2\n' +
202+
':108C6000707172737475767778797A0A6162636469\n' +
203+
':108C700065666768696A6B6C6D6E6F2222220A02F4\n' +
204+
':108C800001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2\n' +
205+
':00000001FF',
206+
fileAddress: 0x38c00,
207+
fsSize: 144,
208+
bytes() {
209+
return MemoryMap.fromHex(this.hex).get(this.fileAddress);
210+
},
211+
};
212+
// Using space except the last byte should only use a single chunk
213+
const fullChunkMinus = {
214+
fileName: 'one_chunk_minus.py',
215+
fileStr:
216+
'a = """abcdefghijklmnopqrstuvwxyz\n' +
217+
'abcdefghijklmnopqrstuvwxyz\n' +
218+
'abcdefghijklmnopqrstuvwxyz\n' +
219+
'abcdefghijklm"""\n',
220+
hex:
221+
':020000040003F7\n' +
222+
':108C0000FE7D126F6E655F6368756E6B5F6D696E7A\n' +
223+
':108C100075732E707961203D202222226162636487\n' +
224+
':108C200065666768696A6B6C6D6E6F70717273747C\n' +
225+
':108C300075767778797A0A616263646566676869D0\n' +
226+
':108C40006A6B6C6D6E6F707172737475767778790C\n' +
227+
':108C50007A0A6162636465666768696A6B6C6D6EE7\n' +
228+
':108C60006F707172737475767778797A0A6162635E\n' +
229+
':108C70006465666768696A6B6C6D2222220AFFFF71\n' +
230+
':00000001FF',
231+
fileAddress: 0x38c00,
232+
fsSize: 128,
233+
bytes() {
234+
return MemoryMap.fromHex(this.hex).get(this.fileAddress);
235+
},
236+
};
237+
// One full chunk + a single byte on the second
238+
const twoChunks = {
239+
fileName: 'two_chunks.py',
240+
fileStr:
241+
'a = """abcdefghijklmnopqrstuvwxyz\n' +
242+
'abcdefghijklmnopqrstuvwxyz\n' +
243+
'abcdefghijklmnopqrstuvwxyz\n' +
244+
'abcdefghijklmnopqrst"""\n',
245+
hex:
246+
':020000040003F7\n' +
247+
':108C0000FE010D74776F5F6368756E6B732E7079FC\n' +
248+
':108C100061203D2022222261626364656667686983\n' +
249+
':108C20006A6B6C6D6E6F707172737475767778792C\n' +
250+
':108C30007A0A6162636465666768696A6B6C6D6E07\n' +
251+
':108C40006F707172737475767778797A0A6162637E\n' +
252+
':108C50006465666768696A6B6C6D6E6F707172735C\n' +
253+
':108C60007475767778797A0A616263646566676895\n' +
254+
':108C7000696A6B6C6D6E6F7071727374222222025E\n' +
255+
':108C8000010AFFFFFFFFFFFFFFFFFFFFFFFFFFFFE7\n' +
256+
':00000001FF',
257+
fileAddress: 0x38c00,
258+
fsSize: 144,
259+
bytes() {
260+
return MemoryMap.fromHex(this.hex).get(this.fileAddress);
261+
},
262+
};
263+
264+
it('Can generate a full chunk that also uses the next one.', () => {
265+
const fwWithFsOther = addIntelHexFile(
266+
uPyHexFile,
267+
fullChunkPlus.fileName,
268+
strToBytes(fullChunkPlus.fileStr)
269+
);
270+
271+
const opMap = MemoryMap.fromHex(fwWithFsOther);
272+
const readFileData = opMap
273+
.slice(fullChunkPlus.fileAddress, fullChunkPlus.fsSize)
274+
.get(fullChunkPlus.fileAddress);
275+
expect(readFileData).toEqual(fullChunkPlus.bytes());
276+
});
277+
278+
it('Correctly generate an almost full chunk (not using last byte).', () => {
279+
const fwWithFsOther = addIntelHexFile(
280+
uPyHexFile,
281+
fullChunkMinus.fileName,
282+
strToBytes(fullChunkMinus.fileStr)
283+
);
284+
285+
const opMap = MemoryMap.fromHex(fwWithFsOther);
286+
const readFileData = opMap
287+
.slice(fullChunkMinus.fileAddress, fullChunkMinus.fsSize)
288+
.get(fullChunkMinus.fileAddress);
289+
expect(readFileData).toEqual(fullChunkMinus.bytes());
290+
});
291+
292+
it('Correctlty generate just over a full chunk.', () => {
293+
const fwWithFsOther = addIntelHexFile(
294+
uPyHexFile,
295+
twoChunks.fileName,
296+
strToBytes(twoChunks.fileStr)
297+
);
298+
299+
const opMap = MemoryMap.fromHex(fwWithFsOther);
300+
const readFileData = opMap
301+
.slice(twoChunks.fileAddress, twoChunks.fsSize)
302+
.get(twoChunks.fileAddress);
303+
expect(readFileData).toEqual(twoChunks.bytes());
304+
});
305+
180306
it('Empty file name throws an error.', () => {
181307
const failCase = () => addIntelHexFile(uPyHexFile, '', randContent);
182308

@@ -266,11 +392,6 @@ describe('Writing files to the filesystem.', () => {
266392
});
267393

268394
describe('Reading files from the filesystem.', () => {
269-
// All the files generated below have been created by transferring the files
270-
// to a micro:bit running MicroPython v1.0.1 using Mu.
271-
// Because the filesystem limits depend on the MicroPython version, these will
272-
// only work if combined with v1.0.1.
273-
274395
const alastFilename = 'alast.py';
275396
const alastContent = strToBytes(
276397
'# Lorem Ipsum is simply dummy text of the printing and\n' +
@@ -410,7 +531,7 @@ describe('Reading files from the filesystem.', () => {
410531
'display.scroll(full)\n' +
411532
'print(full)\n'
412533
);
413-
// Uses chunk 0xCA, 0xCB, 0xCC
534+
// Uses chunks 0xCA, 0xCB, 0xCC
414535
const mainHex =
415536
':020000040003F7\n' +
416537
':10F08000FE37076D61696E2E707966726F6D206D47\n' +
@@ -436,19 +557,16 @@ describe('Reading files from the filesystem.', () => {
436557
':00000001FF\n';
437558

438559
it('Can read files of different sizes in non-consecutive locations.', () => {
560+
const addHexToMap = (hexMap: MemoryMap, hex: string) => {
561+
const newMemMap = MemoryMap.fromHex(hex);
562+
newMemMap.forEach((value: Uint8Array, index: number) => {
563+
hexMap.set(index, value);
564+
});
565+
};
439566
const fullUpyFsMemMap = MemoryMap.fromHex(uPyHexFile);
440-
const afirstMemMap = MemoryMap.fromHex(afirstHex);
441-
afirstMemMap.forEach((value: Uint8Array, index: number) => {
442-
fullUpyFsMemMap.set(index, value);
443-
});
444-
const alastMemMap = MemoryMap.fromHex(alastHex);
445-
alastMemMap.forEach((value: Uint8Array, index: number) => {
446-
fullUpyFsMemMap.set(index, value);
447-
});
448-
const mainMemMap = MemoryMap.fromHex(mainHex);
449-
mainMemMap.forEach((value: Uint8Array, index: number) => {
450-
fullUpyFsMemMap.set(index, value);
451-
});
567+
addHexToMap(fullUpyFsMemMap, afirstHex);
568+
addHexToMap(fullUpyFsMemMap, alastHex);
569+
addHexToMap(fullUpyFsMemMap, mainHex);
452570

453571
const foundFiles = getIntelHexFiles(fullUpyFsMemMap.asHexString());
454572

@@ -457,6 +575,8 @@ describe('Reading files from the filesystem.', () => {
457575
expect(foundFiles).toHaveProperty([mainFilename], mainContent);
458576
});
459577

578+
// When MicroPython saves a file that takes full chunk it still utilises
579+
// the next chunk and leaves it empty
460580
const oneChunkPlusFilename = 'one_chunk_plus.py';
461581
const oneChunkPlusContent =
462582
'a = """abcdefghijklmnopqrstuvwxyz\n' +
@@ -588,7 +708,7 @@ describe('Calculate sizes.', () => {
588708
expect(totalSize).toEqual(27 * 1024);
589709
});
590710

591-
it('Calculate the space ocupied for a file in the fs.', () => {
711+
it('Calculate the space occupied for a file in the fs.', () => {
592712
const fileSizeOne = calculateFileSize(
593713
'one_chunk.txt',
594714
new Uint8Array([30, 31, 32, 33, 34])

src/micropython-fs-builder.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -197,12 +197,16 @@ class FsFile {
197197
}
198198
this._dataBytes = data;
199199
// Generate a single byte array with the filesystem data bytes.
200+
// When MicroPython uses up to the last byte of the last chunk it will
201+
// still consume the next chunk, and leave it blank
202+
// To replicate the same behaviour we add an extra 0xFF to the data block
200203
const fileHeader = this._generateFileHeaderBytes();
201204
this._fsDataBytes = new Uint8Array(
202-
fileHeader.length + this._dataBytes.length
205+
fileHeader.length + this._dataBytes.length + 1
203206
);
204207
this._fsDataBytes.set(fileHeader, 0);
205208
this._fsDataBytes.set(this._dataBytes, fileHeader.length);
209+
this._fsDataBytes[this._fsDataBytes.length - 1] = 0xff;
206210
}
207211

208212
/**
@@ -271,12 +275,7 @@ class FsFile {
271275
* flash memory.
272276
*/
273277
getFsFileSize(): number {
274-
let chunksUsed = Math.ceil(this._fsDataBytes.length / CHUNK_DATA_LEN);
275-
// When MicroPython uses up to the last byte of the last chunk it will
276-
// still consume the next chunk, even if it doesn't add any data to it
277-
if (!(this._fsDataBytes.length % CHUNK_DATA_LEN)) {
278-
chunksUsed += 1;
279-
}
278+
const chunksUsed = Math.ceil(this._fsDataBytes.length / CHUNK_DATA_LEN);
280279
return chunksUsed * CHUNK_LEN;
281280
}
282281

0 commit comments

Comments
 (0)