From 8500058d1aeac527b46222ff59071b11b9dc8fda Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Thu, 11 Jun 2020 17:30:45 +0200 Subject: [PATCH 01/16] Added MAPPING.md as common guidelines for type-conversion --- MAPPING.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 MAPPING.md diff --git a/MAPPING.md b/MAPPING.md new file mode 100644 index 0000000..b0ae1c5 --- /dev/null +++ b/MAPPING.md @@ -0,0 +1,8 @@ +| C++ Datatype | C++/Dokany Makro | Size (Byte/bit) | java/JNA Datatyp | +|---------------------|------------------|-----------------|---------------------------------------------| +| char/byte | UCHAR | 1B/8b | byte | +| unsigned short | USHORT | 2B/16b | com.sun.jna.platform.win32.WinDef.USHORT | +| unsigned long (int) | ULONG | 4B/32b | com.sun.jna.platform.win32.WinDef.ULONG | +| unsigned long (int) | DWORD/AccessMask | 4B/32b | com.sun.jna.platform.win32.WinDef.DWORD | +| unsigned __int64 | ULONG64 | 8B/64b | com.sun.jna.platform.win32.WinDef.ULONGLONG | +| | | | | \ No newline at end of file From cc46868a910d8679e34bd6bc9439fd0b83d8ac8e Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Tue, 16 Jun 2020 19:44:12 +0200 Subject: [PATCH 02/16] Updated MAPPING.md with references and updated Data Types --- MAPPING.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/MAPPING.md b/MAPPING.md index b0ae1c5..f05f947 100644 --- a/MAPPING.md +++ b/MAPPING.md @@ -1,8 +1,12 @@ -| C++ Datatype | C++/Dokany Makro | Size (Byte/bit) | java/JNA Datatyp | -|---------------------|------------------|-----------------|---------------------------------------------| -| char/byte | UCHAR | 1B/8b | byte | -| unsigned short | USHORT | 2B/16b | com.sun.jna.platform.win32.WinDef.USHORT | -| unsigned long (int) | ULONG | 4B/32b | com.sun.jna.platform.win32.WinDef.ULONG | -| unsigned long (int) | DWORD/AccessMask | 4B/32b | com.sun.jna.platform.win32.WinDef.DWORD | -| unsigned __int64 | ULONG64 | 8B/64b | com.sun.jna.platform.win32.WinDef.ULONGLONG | -| | | | | \ No newline at end of file +# Overview +| C Data Type | Windows Data Type - Macro | Size (Bytes/bits) | java/JNA Data Type | +|---------------------|---------------------------|-------------------|-----------------------------------| +| char/byte | UCHAR | 1B/8b | byte | +| unsigned short | USHORT | 2B/16b | short (@Unsigned)/WinDef.USHORT | +| unsigned long (int) | ULONG | 4B/32b | int (@Unsigned)/WinDef.ULONG | +| unsigned long (int) | DWORD/AccessMask | 4B/32b | int (@Unsigned)/WinDef.DWORD | +| unsigned __int64 | ULONG64 | 8B/64b | long (@Unsigned)/WinDef.ULONGLONG | +# References +Basic Types/Size: [Web](https://docs.microsoft.com/en-us/cpp/c-language/storage-of-basic-types?view=vs-2019) | [Permalink](https://web.archive.org/web/20200616151823/https://docs.microsoft.com/en-us/cpp/c-language/storage-of-basic-types?view=vs-2019)
+Windows Data Types/Mapped C Data Types: [Web](https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types) | [Permalink](https://web.archive.org/web/20200616152122/https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types)
+JNA Data Types: [Web](https://java-native-access.github.io/jna/4.2.1/com/sun/jna/platform/win32/WinDef.html) | [Permalink](https://web.archive.org/web/20200616153134/http://java-native-access.github.io/jna/4.2.1/com/sun/jna/platform/win32/WinDef.html)
\ No newline at end of file From 84bbb920391f65ae95af1cd24f61fdcc186a2148 Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Tue, 16 Jun 2020 21:08:50 +0200 Subject: [PATCH 03/16] Added Annotation (Unsigned) and fixed bugs Added new Annotation (Unsigned) used to indicate that an integer type or integer type pointer refers to an unsigned value. Used Unsigned-Annotation to mark parameter length in getDokanControlList method in DokanControl and all calls that lead to it Changed getDokanControlList to use an unsigned int instead of a long (matching Dokany that uses ULONG) and finished method (removed TODO) Changed length of array MountPoint to 260 to match Dokany --- .../dokan_java/AbstractDokanFileSystem.java | 12 +++++---- .../dokan/dokan_java/DokanNativeMethods.java | 5 ++-- .../java/dev/dokan/dokan_java/Unsigned.java | 17 ++++++++++++ .../dokan_java/structure/DokanControl.java | 26 ++++++++++++------- 4 files changed, 43 insertions(+), 17 deletions(-) create mode 100644 src/main/java/dev/dokan/dokan_java/Unsigned.java diff --git a/src/main/java/dev/dokan/dokan_java/AbstractDokanFileSystem.java b/src/main/java/dev/dokan/dokan_java/AbstractDokanFileSystem.java index 6afa1af..1b71e35 100644 --- a/src/main/java/dev/dokan/dokan_java/AbstractDokanFileSystem.java +++ b/src/main/java/dev/dokan/dokan_java/AbstractDokanFileSystem.java @@ -5,12 +5,12 @@ import com.sun.jna.Native; import com.sun.jna.Pointer; import com.sun.jna.WString; -import com.sun.jna.ptr.LongByReference; +import com.sun.jna.ptr.IntByReference; import dev.dokan.dokan_java.constants.dokany.MountError; import dev.dokan.dokan_java.constants.dokany.MountOption; +import dev.dokan.dokan_java.masking.MaskValueSet; import dev.dokan.dokan_java.structure.DokanControl; import dev.dokan.dokan_java.structure.DokanOptions; -import dev.dokan.dokan_java.masking.MaskValueSet; import java.lang.reflect.Method; import java.nio.file.Path; @@ -248,9 +248,11 @@ public final synchronized void unmount() { private boolean volumeIsStillMounted() { char[] mntPtCharArray = mountPoint.toAbsolutePath().toString().toCharArray(); - LongByReference length = new LongByReference(); - Pointer startOfList = DokanNativeMethods.DokanGetMountPointList(false, length); - List list = DokanControl.getDokanControlList(startOfList, length.getValue()); + IntByReference lengthPointer = new IntByReference(); + Pointer startOfList = DokanNativeMethods.DokanGetMountPointList(false, lengthPointer); + + @Unsigned int length = lengthPointer.getValue(); + List list = DokanControl.getDokanControlList(startOfList, length); // It is not enough that the entry.MountPoint contains the actual mount point. It also has to ends afterwards. boolean mountPointInList = list.stream().anyMatch(entry -> Arrays.equals(entry.MountPoint, 12, 12 + mntPtCharArray.length, mntPtCharArray, 0, mntPtCharArray.length) diff --git a/src/main/java/dev/dokan/dokan_java/DokanNativeMethods.java b/src/main/java/dev/dokan/dokan_java/DokanNativeMethods.java index 637cc55..d878d27 100644 --- a/src/main/java/dev/dokan/dokan_java/DokanNativeMethods.java +++ b/src/main/java/dev/dokan/dokan_java/DokanNativeMethods.java @@ -6,7 +6,6 @@ import com.sun.jna.WString; import com.sun.jna.platform.win32.WinNT; import com.sun.jna.ptr.IntByReference; -import com.sun.jna.ptr.LongByReference; import com.sun.jna.win32.StdCallLibrary; import dev.dokan.dokan_java.constants.dokany.MountError; import dev.dokan.dokan_java.structure.DokanControl; @@ -200,10 +199,10 @@ static native void DokanMapKernelToUserCreateFileFlags( *

* * @param uncOnly - Get only instances that have UNC Name. - * @param nbRead - Number of instances successfully retrieved + * @param nbRead - {@link Unsigned} Number of instances successfully retrieved * @return a pointer to the start of the allocated array of {@link DokanControl} elemets. */ - static native Pointer DokanGetMountPointList(boolean uncOnly, LongByReference nbRead); + static native Pointer DokanGetMountPointList(boolean uncOnly, @Unsigned IntByReference nbRead); /** * Release Mount point list resources from {@link #DokanGetMountPointList}. diff --git a/src/main/java/dev/dokan/dokan_java/Unsigned.java b/src/main/java/dev/dokan/dokan_java/Unsigned.java new file mode 100644 index 0000000..b85a358 --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/Unsigned.java @@ -0,0 +1,17 @@ +package dev.dokan.dokan_java; + + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; + + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(value = {METHOD, FIELD, PARAMETER, LOCAL_VARIABLE, TYPE_PARAMETER, TYPE_USE}) +public @interface Unsigned { + +} diff --git a/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java b/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java index 45375a5..e133921 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java +++ b/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java @@ -4,6 +4,7 @@ import com.sun.jna.Pointer; import com.sun.jna.Structure; import com.sun.jna.platform.win32.WinNT; +import dev.dokan.dokan_java.Unsigned; import java.util.ArrayList; import java.util.Arrays; @@ -24,7 +25,7 @@ public class DokanControl extends Structure implements Structure.ByReference { /** * Mount point. Can be "M:\" (drive letter) or "C:\mount\dokan" (path in NTFS) */ - public char[] MountPoint = new char[256]; + public char[] MountPoint = new char[260]; /** * UNC name used for network volume @@ -79,19 +80,26 @@ protected List getFieldOrder() { * @param length the number of elements in the array. Also acquired with the native method call. * @return a list of DokanControl structures */ - public static List getDokanControlList(Pointer start, long length) { + public static List getDokanControlList(Pointer start, @Unsigned int length) { //TODO Relocate List list = new ArrayList<>(); - if (length == 0) { - return list; - } else if (length < 0) { - //TODO length is actually an unsigned long! -> java always treats them as signed - return list; - } else { + /* + * Let's do the math: + * A list that uses an unsigned int (32 bit) as index could save up to 2^32 objects. + * Even if it only saved one byte in each entry (not accounting for the actual overhead of the entries themselves), + * that would be 2^32 bytes = 4 GB for that list alone! + * If the list of active Dokan MountPoints exceeds 2^31 (signed int) entries, we probably have other problems. + * + * But let's assume that this could still happen: + * In this case we want the application to crash so that someone else can fix that nonsense. + * "assert" seems like a good choice for this case (for once). + */ + assert !(length < 0); + if (length != 0) { list.add(new DokanControl(start)); for (int i = 1; i < length; i++) { list.add(new DokanControl(start, i * list.get(0).size())); } - return list; } + return list; } } From 3b9dfa2d2bf96113ee42ec8d8724618568cc9dc5 Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Tue, 16 Jun 2020 22:36:16 +0200 Subject: [PATCH 04/16] Updated DokanControl Updated DokanControl to use WinNT.MAX_PATH and rewrote getDokanControlList --- .../dev/dokan/dokan_java/structure/DokanControl.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java b/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java index e133921..231ad13 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java +++ b/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java @@ -25,7 +25,7 @@ public class DokanControl extends Structure implements Structure.ByReference { /** * Mount point. Can be "M:\" (drive letter) or "C:\mount\dokan" (path in NTFS) */ - public char[] MountPoint = new char[260]; + public char[] MountPoint = new char[WinNT.MAX_PATH]; /** * UNC name used for network volume @@ -95,9 +95,11 @@ public static List getDokanControlList(Pointer start, @Unsigned in */ assert !(length < 0); if (length != 0) { - list.add(new DokanControl(start)); - for (int i = 1; i < length; i++) { - list.add(new DokanControl(start, i * list.get(0).size())); + long offset = 0; + for(int i = 0; i < length; i++) { + DokanControl control = new DokanControl(start, offset); + list.add(control); + offset += control.size(); } } return list; From 68c0df86fbfa8965aa9f13d8681db2a42ce04de4 Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Tue, 16 Jun 2020 22:44:40 +0200 Subject: [PATCH 05/16] Corrected fields in structures and fixed wrong types Corrected types of fields in structures to match Dokany FIxed wrong types/usage in Constructor in DokanControl FIxed wrong parameters in Constructor in DokanOptions Fixed wrong parameters and added Unsigned-Annotation in accessors of the constructer --- .../dokan/dokan_java/AbstractDokanFileSystem.java | 10 +++++----- src/main/java/dev/dokan/dokan_java/Mountable.java | 2 +- .../dokan/dokan_java/structure/DokanControl.java | 14 +++++++------- .../dokan/dokan_java/structure/DokanOptions.java | 13 +++++++------ 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/main/java/dev/dokan/dokan_java/AbstractDokanFileSystem.java b/src/main/java/dev/dokan/dokan_java/AbstractDokanFileSystem.java index 1b71e35..a2d4d65 100644 --- a/src/main/java/dev/dokan/dokan_java/AbstractDokanFileSystem.java +++ b/src/main/java/dev/dokan/dokan_java/AbstractDokanFileSystem.java @@ -176,7 +176,7 @@ private boolean isImplemented(String funcName) { * @param options an {@link MaskValueSet} containing {@link MountOption}s */ @Override - public final synchronized void mount(Path mountPoint, String volumeName, int volumeSerialnumber, boolean blocking, long timeout, long allocationUnitSize, long sectorSize, String UNCName, short threadCount, MaskValueSet options) { + public final synchronized void mount(Path mountPoint, String volumeName, int volumeSerialnumber, boolean blocking, @Unsigned int timeout, @Unsigned int allocationUnitSize, @Unsigned int sectorSize, String UNCName, @Unsigned short threadCount, MaskValueSet options) { this.dokanOptions = new DokanOptions(mountPoint.toString(), threadCount, options, UNCName, timeout, allocationUnitSize, sectorSize); this.mountPoint = mountPoint; this.volumeName = volumeName; @@ -218,10 +218,10 @@ public final synchronized void mount(Path mountPoint, String volumeName, int vol */ public void mount(Path mountPoint, MaskValueSet mountOptions) { String uncName = null; - short threadCount = 5; - long timeout = 3000; - long allocationUnitSize = 4096; - long sectorsize = 512; + @Unsigned short threadCount = 5; + @Unsigned int timeout = 3000; + @Unsigned int allocationUnitSize = 4096; + @Unsigned int sectorsize = 512; String volumeName = "DOKAN"; int volumeSerialnumber = 30975; mount(mountPoint, volumeName, volumeSerialnumber, false, timeout, allocationUnitSize, sectorsize, uncName, threadCount, mountOptions); diff --git a/src/main/java/dev/dokan/dokan_java/Mountable.java b/src/main/java/dev/dokan/dokan_java/Mountable.java index 24106bb..4ac4e29 100644 --- a/src/main/java/dev/dokan/dokan_java/Mountable.java +++ b/src/main/java/dev/dokan/dokan_java/Mountable.java @@ -27,7 +27,7 @@ public interface Mountable extends AutoCloseable { * @param threadCount the number of threads spawned for processing filesystem calls * @param options an {@link MaskValueSet} containing {@link MountOption}s */ - void mount(Path mountPoint, String volumeName, int volumeSerialnumber, boolean blocking, long timeout, long allocationUnitSize, long sectorSize, String UNCName, short threadCount, MaskValueSet options); + void mount(Path mountPoint, String volumeName, int volumeSerialnumber, boolean blocking, @Unsigned int timeout, @Unsigned int allocationUnitSize, @Unsigned int sectorSize, String UNCName, @Unsigned short threadCount, MaskValueSet options); /** * Unmount this object. diff --git a/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java b/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java index 231ad13..04782ad 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java +++ b/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java @@ -1,5 +1,6 @@ package dev.dokan.dokan_java.structure; +import com.sun.jna.Native; import com.sun.jna.NativeLong; import com.sun.jna.Pointer; import com.sun.jna.Structure; @@ -20,7 +21,7 @@ public class DokanControl extends Structure implements Structure.ByReference { /** * File System Type */ - public long Type; + public int Type; /** * Mount point. Can be "M:\" (drive letter) or "C:\mount\dokan" (path in NTFS) @@ -45,16 +46,15 @@ public class DokanControl extends Structure implements Structure.ByReference { /** * Session ID of calling process */ - public long SessionId; + public int SessionId; public DokanControl(Pointer p) { this(p, 0); } - public DokanControl(Pointer p, long offset) { + public DokanControl(Pointer p, long currentOffset) { super(p); - long currentOffset = offset; - this.Type = p.getLong(currentOffset); + this.Type = p.getInt(currentOffset); currentOffset += NativeLong.SIZE; this.MountPoint = p.getCharArray(currentOffset, WinNT.MAX_PATH); currentOffset += WinNT.MAX_PATH * 2; @@ -63,8 +63,8 @@ public DokanControl(Pointer p, long offset) { this.DeviceName = p.getCharArray(currentOffset, 64); currentOffset += 64 * 2; this.DeviceObject = new Pointer(p.getLong(currentOffset)); - currentOffset += NativeLong.SIZE; - this.SessionId = p.getLong(currentOffset); + currentOffset += Native.POINTER_SIZE; + this.SessionId = p.getInt(currentOffset); } diff --git a/src/main/java/dev/dokan/dokan_java/structure/DokanOptions.java b/src/main/java/dev/dokan/dokan_java/structure/DokanOptions.java index 993ce25..108f669 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/DokanOptions.java +++ b/src/main/java/dev/dokan/dokan_java/structure/DokanOptions.java @@ -1,10 +1,11 @@ package dev.dokan.dokan_java.structure; -import dev.dokan.dokan_java.DokanNativeMethods; -import dev.dokan.dokan_java.constants.dokany.MountOption; import com.sun.jna.Structure; import com.sun.jna.WString; +import dev.dokan.dokan_java.DokanNativeMethods; +import dev.dokan.dokan_java.Unsigned; +import dev.dokan.dokan_java.constants.dokany.MountOption; import dev.dokan.dokan_java.masking.MaskValueSet; import java.util.Arrays; @@ -53,23 +54,23 @@ public class DokanOptions extends Structure implements Structure.ByReference { /** * Max timeout in milliseconds of each request before Dokan gives up to wait events to complete. */ - public long Timeout; + public int Timeout; /** * Allocation Unit Size of the volume. This will affect the file size. */ - public long AllocationUnitSize; + public int AllocationUnitSize; /** * Sector Size of the volume. This will affect then file size. */ - public long SectorSize; + public int SectorSize; public DokanOptions() { } - public DokanOptions(final String mountPoint, final short threadCount, final MaskValueSet mountOptions, final String uncName, final long timeout, final long allocationUnitSize, final long sectorSize) { + public DokanOptions(String mountPoint, @Unsigned short threadCount, MaskValueSet mountOptions, String uncName, @Unsigned int timeout, @Unsigned int allocationUnitSize, @Unsigned int sectorSize) { MountPoint = new WString(mountPoint); ThreadCount = threadCount; Options = mountOptions.intValue(); From e9fd25f815e7ac1e27102461d323fb8640bb5fb8 Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Tue, 16 Jun 2020 22:47:40 +0200 Subject: [PATCH 06/16] Added Unsigned-Annotation to matching fields in all structures --- .../structure/ByHandleFileInformation.java | 14 +++++++++++--- .../dokan_java/structure/DokanAccessState.java | 6 +++++- .../dokan/dokan_java/structure/DokanControl.java | 3 +++ .../dokan/dokan_java/structure/DokanFileInfo.java | 6 +++++- .../structure/DokanIOSecurityContext.java | 2 ++ .../dokan/dokan_java/structure/DokanOptions.java | 7 +++++++ .../dokan/dokan_java/structure/UnicodeString.java | 3 +++ 7 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/main/java/dev/dokan/dokan_java/structure/ByHandleFileInformation.java b/src/main/java/dev/dokan/dokan_java/structure/ByHandleFileInformation.java index 6dc8346..0465f10 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/ByHandleFileInformation.java +++ b/src/main/java/dev/dokan/dokan_java/structure/ByHandleFileInformation.java @@ -1,12 +1,13 @@ package dev.dokan.dokan_java.structure; -import dev.dokan.dokan_java.DokanOperations; -import dev.dokan.dokan_java.DokanUtils; -import dev.dokan.dokan_java.constants.microsoft.FileAttribute; import com.sun.jna.Structure; import com.sun.jna.platform.win32.WinBase; import com.sun.jna.platform.win32.WinBase.FILETIME; import com.sun.jna.platform.win32.WinNT; +import dev.dokan.dokan_java.DokanOperations; +import dev.dokan.dokan_java.DokanUtils; +import dev.dokan.dokan_java.Unsigned; +import dev.dokan.dokan_java.constants.microsoft.FileAttribute; import dev.dokan.dokan_java.masking.MaskValueSet; import java.nio.file.Path; @@ -39,6 +40,7 @@ public class ByHandleFileInformation extends Structure implements Structure.ByRe * The file attributes of a file. For possible values and their descriptions, see File Attribute Constants. The FILE_ATTRIBUTE_SPARSE_FILE attribute on the file is set if any of the streams of the file have ever been * sparse. */ + @Unsigned public int dwFileAttributes; /** @@ -61,31 +63,37 @@ public class ByHandleFileInformation extends Structure implements Structure.ByRe /** * The serial number of the volume that contains a file. */ + @Unsigned public int dwVolumeSerialNumber; /** * The high-order DWORD value of the file size, in bytes. This value is zero unless the file size is greater than MAXDWORD. The size of the file is equal to (nFileSizeHigh * (MAXDWORD+1)) + nFileSizeLow. */ + @Unsigned public int nFileSizeHigh; /** * The low-order DWORD value of the file size, in bytes. */ + @Unsigned public int nFileSizeLow; /** * The high-order DWORD value of the file size, in bytes. This value is zero unless the file size is greater than MAXDWORD. The size of the file is equal to (nFileSizeHigh* (MAXDWORD+1)) + nFileSizeLow. */ + @Unsigned public int nFileIndexHigh; /** * The low-order DWORD value of the file size, in bytes. */ + @Unsigned public int nFileIndexLow; /** * The number of links to this file. For the FAT file system this member is always 1. For the NTFS file system, it can be more than 1. */ + @Unsigned public int nNumberOfLinks = 1; private Path filePath; diff --git a/src/main/java/dev/dokan/dokan_java/structure/DokanAccessState.java b/src/main/java/dev/dokan/dokan_java/structure/DokanAccessState.java index 73cec94..cc74f25 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/DokanAccessState.java +++ b/src/main/java/dev/dokan/dokan_java/structure/DokanAccessState.java @@ -1,9 +1,9 @@ package dev.dokan.dokan_java.structure; -import com.sun.jna.Pointer; import com.sun.jna.Structure; import com.sun.jna.platform.win32.WinNT; +import dev.dokan.dokan_java.Unsigned; import java.util.Arrays; import java.util.List; @@ -48,6 +48,7 @@ public class DokanAccessState extends Structure { * A driver can also check for the TOKEN_IS_RESTRICTED flag. * These flags are defined in Ntifs.h. */ + @Unsigned public int Flags; /** @@ -55,17 +56,20 @@ public class DokanAccessState extends Structure { * A driver uses this member to determine if the Windows security system can grant access. * If access can be granted, the driver updates the PreviouslyGrantedAccess and RemainingDesiredAccess members accordingly. */ + @Unsigned public int RemainingDesiredAccess; /** * An ACCESS_MASK type that specifies the information about access that has already been granted to the caller of one of the Security Reference Monitor Routines * The Windows security system grants certain rights based on the privileges of the caller, such as traverse right (the ability to traverse through a directory as part of opening a subdirectory or file). */ + @Unsigned public int PreviouslyGrantedAccess; /** * An ACCESS_MASK type that contains the original access rights that were requested by the caller. */ + @Unsigned public int OriginalDesiredAccess; /** diff --git a/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java b/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java index 04782ad..6d1b052 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java +++ b/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java @@ -1,5 +1,6 @@ package dev.dokan.dokan_java.structure; + import com.sun.jna.Native; import com.sun.jna.NativeLong; import com.sun.jna.Pointer; @@ -21,6 +22,7 @@ public class DokanControl extends Structure implements Structure.ByReference { /** * File System Type */ + @Unsigned public int Type; /** @@ -46,6 +48,7 @@ public class DokanControl extends Structure implements Structure.ByReference { /** * Session ID of calling process */ + @Unsigned public int SessionId; public DokanControl(Pointer p) { diff --git a/src/main/java/dev/dokan/dokan_java/structure/DokanFileInfo.java b/src/main/java/dev/dokan/dokan_java/structure/DokanFileInfo.java index 4cd8a98..83f0bb4 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/DokanFileInfo.java +++ b/src/main/java/dev/dokan/dokan_java/structure/DokanFileInfo.java @@ -1,8 +1,9 @@ package dev.dokan.dokan_java.structure; +import com.sun.jna.Structure; import dev.dokan.dokan_java.DokanNativeMethods; import dev.dokan.dokan_java.DokanOperations; -import com.sun.jna.Structure; +import dev.dokan.dokan_java.Unsigned; import java.util.Arrays; import java.util.List; @@ -18,6 +19,7 @@ public class DokanFileInfo extends Structure implements Structure.ByReference { * Context that can be used to carry information between operation. The context can carry whatever type like {@link com.sun.jna.platform.win32.WinNT.HANDLE}, {@link Structure}, {@link com.sun.jna.ptr.IntByReference}, * {@link com.sun.jna.Pointer} that will help the implementation understand the request context of the event. */ + @Unsigned public long Context; /** @@ -28,6 +30,7 @@ public class DokanFileInfo extends Structure implements Structure.ByReference { /** * Reserved. Used internally by Dokan library. Never modify. */ + @Unsigned public long DokanContext; /** @@ -53,6 +56,7 @@ public class DokanFileInfo extends Structure implements Structure.ByReference { /** * Process ID for the thread that originally requested a given I/O operation. */ + @Unsigned public int ProcessId; /** diff --git a/src/main/java/dev/dokan/dokan_java/structure/DokanIOSecurityContext.java b/src/main/java/dev/dokan/dokan_java/structure/DokanIOSecurityContext.java index a4a17a3..377c1b1 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/DokanIOSecurityContext.java +++ b/src/main/java/dev/dokan/dokan_java/structure/DokanIOSecurityContext.java @@ -3,6 +3,7 @@ import com.sun.jna.Structure; import com.sun.jna.WString; +import dev.dokan.dokan_java.Unsigned; /** @@ -23,6 +24,7 @@ public class DokanIOSecurityContext extends Structure implements Structure.ByRef /** * An ACCESS_MASK value that expresses the access rights that are requested in the IRP_MJ_CREATE request. */ + @Unsigned public int DesiredAccess; } diff --git a/src/main/java/dev/dokan/dokan_java/structure/DokanOptions.java b/src/main/java/dev/dokan/dokan_java/structure/DokanOptions.java index 108f669..7414f84 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/DokanOptions.java +++ b/src/main/java/dev/dokan/dokan_java/structure/DokanOptions.java @@ -22,21 +22,25 @@ public class DokanOptions extends Structure implements Structure.ByReference { /** * Version of the Dokan features requested (version "123" is equal to Dokan version 1.2.3). */ + @Unsigned public short Version = DokanNativeMethods.getMinimumRequiredDokanVersion(); /** * Number of threads to be used internally by Dokan library. More thread will handle more events at the same time. */ + @Unsigned public short ThreadCount; /** * Features enable for the mount. It is a combination of {@link MountOption} masks. */ + @Unsigned public int Options; /** * FileSystem can store anything here */ + @Unsigned public long GlobalContext = 0L; /** @@ -54,16 +58,19 @@ public class DokanOptions extends Structure implements Structure.ByReference { /** * Max timeout in milliseconds of each request before Dokan gives up to wait events to complete. */ + @Unsigned public int Timeout; /** * Allocation Unit Size of the volume. This will affect the file size. */ + @Unsigned public int AllocationUnitSize; /** * Sector Size of the volume. This will affect then file size. */ + @Unsigned public int SectorSize; public DokanOptions() { diff --git a/src/main/java/dev/dokan/dokan_java/structure/UnicodeString.java b/src/main/java/dev/dokan/dokan_java/structure/UnicodeString.java index 9e554db..c49ab6b 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/UnicodeString.java +++ b/src/main/java/dev/dokan/dokan_java/structure/UnicodeString.java @@ -3,6 +3,7 @@ import com.sun.jna.Pointer; import com.sun.jna.Structure; +import dev.dokan.dokan_java.Unsigned; /** @@ -17,11 +18,13 @@ public class UnicodeString extends Structure { /** * The length, in bytes, of the string stored in {@link UnicodeString#Buffer}. */ + @Unsigned public short Length; /** * The length, in bytes, of {@link UnicodeString#Buffer}. */ + @Unsigned public short MaximumLength; /** From 8deca7bdefc7f4aca4a1cefaed4a63d22a3081d4 Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Tue, 16 Jun 2020 23:29:52 +0200 Subject: [PATCH 07/16] Updated MAPPING.md to use correct version of JNA (5.5.0) --- MAPPING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAPPING.md b/MAPPING.md index f05f947..2981248 100644 --- a/MAPPING.md +++ b/MAPPING.md @@ -9,4 +9,4 @@ # References Basic Types/Size: [Web](https://docs.microsoft.com/en-us/cpp/c-language/storage-of-basic-types?view=vs-2019) | [Permalink](https://web.archive.org/web/20200616151823/https://docs.microsoft.com/en-us/cpp/c-language/storage-of-basic-types?view=vs-2019)
Windows Data Types/Mapped C Data Types: [Web](https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types) | [Permalink](https://web.archive.org/web/20200616152122/https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types)
-JNA Data Types: [Web](https://java-native-access.github.io/jna/4.2.1/com/sun/jna/platform/win32/WinDef.html) | [Permalink](https://web.archive.org/web/20200616153134/http://java-native-access.github.io/jna/4.2.1/com/sun/jna/platform/win32/WinDef.html)
\ No newline at end of file +JNA Data Types: [Web](https://java-native-access.github.io/jna/5.5.0/javadoc/com/sun/jna/platform/win32/WinDef.html) | [Permalink](https://web.archive.org/web/20200616212719/https://java-native-access.github.io/jna/5.5.0/javadoc/com/sun/jna/platform/win32/WinDef.html)
\ No newline at end of file From 086f9ea0c2f6de6a30a38d85edcaedb112c1bd28 Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Thu, 18 Jun 2020 17:55:40 +0200 Subject: [PATCH 08/16] Added UnsignedNumbers utility class and marked values as unsigned Added Unsigned-Annotation to all accessors of unsigned value Added UnsignedNumbers class with conversion methods between unsigned integer types and String Fixed toString method of DokanFileInfo and DokanOptions --- .../dev/dokan/dokan_java/UnsignedNumbers.java | 22 ++++++++++++++++ .../examples/DirListingFileSystem.java | 5 ++-- .../structure/ByHandleFileInformation.java | 26 ++++++++++++++----- .../dokan_java/structure/DokanFileInfo.java | 14 +++++++++- .../dokan_java/structure/DokanOptions.java | 13 +++++++++- 5 files changed, 69 insertions(+), 11 deletions(-) create mode 100644 src/main/java/dev/dokan/dokan_java/UnsignedNumbers.java diff --git a/src/main/java/dev/dokan/dokan_java/UnsignedNumbers.java b/src/main/java/dev/dokan/dokan_java/UnsignedNumbers.java new file mode 100644 index 0000000..f412897 --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/UnsignedNumbers.java @@ -0,0 +1,22 @@ +package dev.dokan.dokan_java; + + +public class UnsignedNumbers { + + public static String toUnsignedString(@Unsigned byte value) { + return toUnsignedString(Byte.toUnsignedInt(value)); + } + + public static String toUnsignedString(@Unsigned short value) { + return toUnsignedString(Short.toUnsignedInt(value)); + } + + public static String toUnsignedString(@Unsigned int value) { + return Integer.toUnsignedString(value); + } + + public static String toUnsignedString(@Unsigned long value) { + return Long.toUnsignedString(value); + } + +} \ No newline at end of file diff --git a/src/main/java/dev/dokan/dokan_java/examples/DirListingFileSystem.java b/src/main/java/dev/dokan/dokan_java/examples/DirListingFileSystem.java index e3608a8..bf0d440 100644 --- a/src/main/java/dev/dokan/dokan_java/examples/DirListingFileSystem.java +++ b/src/main/java/dev/dokan/dokan_java/examples/DirListingFileSystem.java @@ -10,6 +10,7 @@ import dev.dokan.dokan_java.DokanOperations; import dev.dokan.dokan_java.DokanUtils; import dev.dokan.dokan_java.FileSystemInformation; +import dev.dokan.dokan_java.Unsigned; import dev.dokan.dokan_java.constants.microsoft.CreateDisposition; import dev.dokan.dokan_java.constants.microsoft.CreateOption; import dev.dokan.dokan_java.constants.microsoft.NtStatuses; @@ -93,7 +94,7 @@ public int zwCreateFile(WString rawPath, DokanIOSecurityContext securityContext, } } - long val = this.handleHandler.incrementAndGet(); + @Unsigned long val = this.handleHandler.incrementAndGet(); if (val == 0) { val = this.handleHandler.incrementAndGet(); } @@ -135,7 +136,7 @@ private ByHandleFileInformation getFileInformation(Path p) throws IOException { if (attr.fileKey() != null) { index = (long) attr.fileKey(); } - int fileAttr = 0; + @Unsigned int fileAttr = 0; fileAttr |= attr.isArchive() ? WinNT.FILE_ATTRIBUTE_ARCHIVE : 0; fileAttr |= attr.isSystem() ? WinNT.FILE_ATTRIBUTE_SYSTEM : 0; fileAttr |= attr.isHidden() ? WinNT.FILE_ATTRIBUTE_HIDDEN : 0; diff --git a/src/main/java/dev/dokan/dokan_java/structure/ByHandleFileInformation.java b/src/main/java/dev/dokan/dokan_java/structure/ByHandleFileInformation.java index 0465f10..bdec47b 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/ByHandleFileInformation.java +++ b/src/main/java/dev/dokan/dokan_java/structure/ByHandleFileInformation.java @@ -7,6 +7,7 @@ import dev.dokan.dokan_java.DokanOperations; import dev.dokan.dokan_java.DokanUtils; import dev.dokan.dokan_java.Unsigned; +import dev.dokan.dokan_java.UnsignedNumbers; import dev.dokan.dokan_java.constants.microsoft.FileAttribute; import dev.dokan.dokan_java.masking.MaskValueSet; @@ -104,7 +105,7 @@ public ByHandleFileInformation() { this(null, null, null); } - public ByHandleFileInformation(Path filePath, int attrs, FileTime creationTime, FileTime lastAccessTime, FileTime lastWriteTime, int volumeSerialNumber, long fileSize, long fileIndex) { + public ByHandleFileInformation(Path filePath, @Unsigned int attrs, FileTime creationTime, FileTime lastAccessTime, FileTime lastWriteTime, @Unsigned int volumeSerialNumber, @Unsigned long fileSize, @Unsigned long fileIndex) { this.filePath = filePath; this.dwFileAttributes = attrs; this.setTimes(creationTime.toMillis(), lastAccessTime.toMillis(), lastWriteTime.toMillis()); @@ -113,7 +114,7 @@ public ByHandleFileInformation(Path filePath, int attrs, FileTime creationTime, this.dwVolumeSerialNumber = volumeSerialNumber; } - public ByHandleFileInformation(Path filePath, MaskValueSet attrs, FileTime creationTime, FileTime lastAccessTime, FileTime lastWriteTime, int volumeSerialNumber, long fileSize, long fileIndex) { + public ByHandleFileInformation(Path filePath, MaskValueSet attrs, FileTime creationTime, FileTime lastAccessTime, FileTime lastWriteTime, @Unsigned int volumeSerialNumber, @Unsigned long fileSize, @Unsigned long fileIndex) { this.filePath = filePath; this.dwFileAttributes = attrs.intValue(); this.setTimes(creationTime.toMillis(), lastAccessTime.toMillis(), lastWriteTime.toMillis()); @@ -196,14 +197,14 @@ public void setCreationTime(final long creationTime) { * * @param sizeToSet the new size of the file */ - public void setFileSize(final long sizeToSet) { + public void setFileSize(@Unsigned final long sizeToSet) { this.fileSize = sizeToSet; final WinNT.LARGE_INTEGER largeInt = new WinNT.LARGE_INTEGER(sizeToSet); this.nFileSizeHigh = largeInt.getHigh().intValue(); this.nFileSizeLow = largeInt.getLow().intValue(); } - protected final void setSizesExplicit(final long size, final int sizeHigh, final int sizeLow) { + protected final void setSizesExplicit(@Unsigned final long size, @Unsigned final int sizeHigh, @Unsigned final int sizeLow) { this.fileSize = size; this.nFileSizeHigh = sizeHigh; this.nFileSizeLow = sizeLow; @@ -213,14 +214,14 @@ public final long getSize() { return this.fileSize; } - public void setIndex(final long index) { + public void setIndex(@Unsigned final long index) { this.fileIndex = index; final WinNT.LARGE_INTEGER largeInt = new WinNT.LARGE_INTEGER(index); this.nFileIndexHigh = largeInt.getHigh().intValue(); this.nFileIndexLow = largeInt.getLow().intValue(); } - protected void setIndexExplicit(final long index, final int indexHigh, final int indexLow) { + protected void setIndexExplicit(@Unsigned final long index, @Unsigned final int indexHigh, @Unsigned final int indexLow) { this.fileIndex = index; this.nFileIndexHigh = indexHigh; this.nFileIndexLow = indexLow; @@ -252,6 +253,17 @@ public List getFieldOrder() { @Override public String toString() { - return "ByHandleFileInfo(filePath=" + this.filePath + ", fileIndex=" + this.fileIndex + ", fileSize=" + this.fileSize + ", nFileIndexHigh=" + this.nFileIndexHigh + ", nFileIndexLow=" + this.nFileIndexLow + ", dwFileAttributes=" + this.dwFileAttributes + ", ftCreationTime=" + this.ftCreationTime + ", ftLastAccessTime=" + this.ftLastAccessTime + ", ftLastWriteTime=" + this.ftLastWriteTime + ", nFileSizeHigh=" + this.nFileSizeHigh + ", nFileSizeLow=" + this.nFileSizeLow + ", dwVolumeSerialNumber=" + this.dwVolumeSerialNumber + ", nNumberOfLinks=" + this.nNumberOfLinks + ")"; + return String.format("ByHandleFileInfo(filePath=%s, fileIndex=%s, fileSize=%s, nFileIndexHigh=%s, nFileIndexLow=%s, dwFileAttributes=%s, ftCreationTime=%s, ftLastAccessTime=%s, ftLastWriteTime=%s, nFileSizeHigh=%s, nFileSizeLow=%s, dwVolumeSerialNumber=%s, nNumberOfLinks=%s)", + this.filePath, + UnsignedNumbers.toUnsignedString(this.fileIndex), + UnsignedNumbers.toUnsignedString(this.fileSize), + UnsignedNumbers.toUnsignedString(this.nFileIndexHigh), + UnsignedNumbers.toUnsignedString(this.nFileIndexLow), + UnsignedNumbers.toUnsignedString(this.dwFileAttributes), + this.ftCreationTime, this.ftLastAccessTime, this.ftLastWriteTime, + UnsignedNumbers.toUnsignedString(this.nFileSizeHigh), + UnsignedNumbers.toUnsignedString(this.nFileSizeLow), + UnsignedNumbers.toUnsignedString(this.dwVolumeSerialNumber), + UnsignedNumbers.toUnsignedString(this.nNumberOfLinks)); } } diff --git a/src/main/java/dev/dokan/dokan_java/structure/DokanFileInfo.java b/src/main/java/dev/dokan/dokan_java/structure/DokanFileInfo.java index 83f0bb4..2dab9ff 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/DokanFileInfo.java +++ b/src/main/java/dev/dokan/dokan_java/structure/DokanFileInfo.java @@ -1,9 +1,11 @@ package dev.dokan.dokan_java.structure; + import com.sun.jna.Structure; import dev.dokan.dokan_java.DokanNativeMethods; import dev.dokan.dokan_java.DokanOperations; import dev.dokan.dokan_java.Unsigned; +import dev.dokan.dokan_java.UnsignedNumbers; import java.util.Arrays; import java.util.List; @@ -103,7 +105,17 @@ public final boolean writeToEndOfFile() { @Override public String toString() { - return "DokanFileInfo(Context=" + this.Context + ", DokanContext=" + this.DokanContext + ", DokanOpts=" + this.DokanOpts + ", ProcessId=" + this.ProcessId + ", IsDirectory=" + this.IsDirectory + ", DeleteOnClose=" + this.DeleteOnClose + ", PagingIo=" + this.PagingIo + ", SynchronousIo=" + this.SynchronousIo + ", Nocache=" + this.Nocache + ", WriteToEndOfFile=" + this.WriteToEndOfFile + ")"; + return String.format("DokanFileInfo(Context=%s, DokanContext=%s, DokanOpts=%s, ProcessId=%s, IsDirectory=%s/%s, DeleteOnClose=%s/%s, PagingIo=%s/%s, SynchronousIo=%s/%s, Nocache=%s/%s, WriteToEndOfFile=%s/%s)", + UnsignedNumbers.toUnsignedString(this.Context), + UnsignedNumbers.toUnsignedString(this.DokanContext), + this.DokanOpts, + UnsignedNumbers.toUnsignedString(this.ProcessId), + this.IsDirectory, isDirectory(), + this.DeleteOnClose, deleteOnClose(), + this.PagingIo, pagingIo(), + this.SynchronousIo, synchronousIo(), + this.Nocache, noCache(), + this.WriteToEndOfFile, writeToEndOfFile()); } } diff --git a/src/main/java/dev/dokan/dokan_java/structure/DokanOptions.java b/src/main/java/dev/dokan/dokan_java/structure/DokanOptions.java index 7414f84..3a163e6 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/DokanOptions.java +++ b/src/main/java/dev/dokan/dokan_java/structure/DokanOptions.java @@ -5,6 +5,7 @@ import com.sun.jna.WString; import dev.dokan.dokan_java.DokanNativeMethods; import dev.dokan.dokan_java.Unsigned; +import dev.dokan.dokan_java.UnsignedNumbers; import dev.dokan.dokan_java.constants.dokany.MountOption; import dev.dokan.dokan_java.masking.MaskValueSet; @@ -102,6 +103,16 @@ protected List getFieldOrder() { @Override public String toString() { - return "DeviceOptions(Version=" + this.Version + ", ThreadCount=" + this.ThreadCount + ", Options=" + this.Options + ", mountOptions=" + this.getMountOptions() + ", GlobalContext=" + this.GlobalContext + ", MountPoint=" + this.MountPoint + ", UNCName=" + this.UNCName + ", Timeout=" + this.Timeout + ", AllocationUnitSize=" + this.AllocationUnitSize + ", SectorSize=" + this.SectorSize + ")"; + return String.format("DeviceOptions(Version=%s, ThreadCount=%s, Options=%s, mountOptions=%s, GlobalContext=%s, MountPoint=%s, UNCName=%s, Timeout=%s, AllocationUnitSize=%s, SectorSize=%s)", + UnsignedNumbers.toUnsignedString(this.Version), + UnsignedNumbers.toUnsignedString(this.ThreadCount), + UnsignedNumbers.toUnsignedString(this.Options), + this.getMountOptions(), + UnsignedNumbers.toUnsignedString(this.GlobalContext), + this.MountPoint, + this.UNCName, + UnsignedNumbers.toUnsignedString(this.Timeout), + UnsignedNumbers.toUnsignedString(this.AllocationUnitSize), + UnsignedNumbers.toUnsignedString(this.SectorSize)); } } From 279b5b890b4b148a86fe25ad1d9b43f9fd7d8507 Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Mon, 22 Jun 2020 21:13:46 +0200 Subject: [PATCH 09/16] Moved my comment and took out the sass Moved my comment and took out the sass to make it more professional See: https://github.com/dokan-dev/dokan-java/pull/48#discussion_r443744844 --- .../dokan_java/structure/DokanControl.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java b/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java index 6d1b052..7b8d0cd 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java +++ b/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java @@ -77,7 +77,14 @@ protected List getFieldOrder() { } /** - * Creates a java {@link List} of {@link DokanControl} strcutures given the pointer returned by NativeMethods#DokanGetMountPointList(boolean, LongByReference). + * Creates a java {@link List} of {@link DokanControl} strcutures given the pointer returned by NativeMethods#DokanGetMountPointList(boolean, LongByReference).
+ *
+ * Implementation note:
+ * Length is an unsigned 32-bit int. Java only supports arrays and lists up to an index size of 231-1 ({@link Integer#MAX_VALUE Integer.MAX_VALUE}). + * A list that exceeds this size is unrealistic (it would need at least 2 GB of space, even if it only stored unique Byte-Objects). + * Any value that exceeds {@link Integer#MAX_VALUE Integer.MAX_VALUE}, has it's 32nd bit set (at least when using Two's complement for storing it). + * The 32nd bit defines the sign of a java int, therefore a {@code length < 0} indicates that this threshold has been reached. + * In this case the application crashes ("fail-fast") to allow someone to take care of this issue. * * @param start the initial pointer returned by the native method * @param length the number of elements in the array. Also acquired with the native method call. @@ -85,17 +92,7 @@ protected List getFieldOrder() { */ public static List getDokanControlList(Pointer start, @Unsigned int length) { //TODO Relocate List list = new ArrayList<>(); - /* - * Let's do the math: - * A list that uses an unsigned int (32 bit) as index could save up to 2^32 objects. - * Even if it only saved one byte in each entry (not accounting for the actual overhead of the entries themselves), - * that would be 2^32 bytes = 4 GB for that list alone! - * If the list of active Dokan MountPoints exceeds 2^31 (signed int) entries, we probably have other problems. - * - * But let's assume that this could still happen: - * In this case we want the application to crash so that someone else can fix that nonsense. - * "assert" seems like a good choice for this case (for once). - */ + assert !(length < 0); if (length != 0) { long offset = 0; From 5895e92ead7046468f4dc4732cfb8c758d530089 Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Mon, 22 Jun 2020 21:36:52 +0200 Subject: [PATCH 10/16] Replaced usage of "assert" with explicit check Replaced usage of "assert" with explicit check to make sure that the check is done, even if assertions are disabled. From the java language guide (linked below): "Do not use assertions for argument checking in public methods. Argument checking is typically part of the published specifications (or contract) of a method, and these specifications must be obeyed whether assertions are enabled or disabled." The next sentence "Another problem with using assertions for argument checking is that erroneous arguments should result in an appropriate runtime exception (such as IllegalArgumentException, IndexOutOfBoundsException, or NullPointerException). An assertion failure will not throw an appropriate exception." doesn't apply in this case as this method isn't part of the API. It's only used internally and this check is implemented solely for the purpose of making sure everything in this library is working fine. See: https://docs.oracle.com/javase/7/docs/technotes/guides/language/assert.html or https://web.archive.org/web/20200622193110/https://docs.oracle.com/javase/7/docs/technotes/guides/language/assert.html --- .../java/dev/dokan/dokan_java/structure/DokanControl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java b/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java index 7b8d0cd..2af2b11 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java +++ b/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java @@ -93,7 +93,9 @@ protected List getFieldOrder() { public static List getDokanControlList(Pointer start, @Unsigned int length) { //TODO Relocate List list = new ArrayList<>(); - assert !(length < 0); + if(length < 0) { + throw new AssertionError(String.format("Illegal length: %s (%d)", Integer.toUnsignedString(length), length)); + } if (length != 0) { long offset = 0; for(int i = 0; i < length; i++) { From 8a15e47731e021e6e3788cce095a9aaaa04d03e9 Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Tue, 23 Jun 2020 00:38:06 +0200 Subject: [PATCH 11/16] Added additional methods to UnsignedNumbers Added methods to compare, divide and get the remainder of unsigned numbers to UnsignedNumbers. See: https://github.com/dokan-dev/dokan-java/pull/48#discussion_r443746630 --- .../dev/dokan/dokan_java/UnsignedNumbers.java | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) diff --git a/src/main/java/dev/dokan/dokan_java/UnsignedNumbers.java b/src/main/java/dev/dokan/dokan_java/UnsignedNumbers.java index f412897..1489eca 100644 --- a/src/main/java/dev/dokan/dokan_java/UnsignedNumbers.java +++ b/src/main/java/dev/dokan/dokan_java/UnsignedNumbers.java @@ -19,4 +19,145 @@ public static String toUnsignedString(@Unsigned long value) { return Long.toUnsignedString(value); } + /** + * Convenience method that stands in for the missing method {@code Byte#divideUnsigned(byte, byte)}.
+ * This method was inspired by {@link Integer#divideUnsigned(int, int) the method of the same in Integer}
+ *
+ * Description copied from {@link Integer#divideUnsigned(int, int)}
+ * Returns the unsigned quotient of dividing the first argument by + * the second where each argument and the result is interpreted as + * an unsigned value. + * + *

Note that in two's complement arithmetic, the three other + * basic arithmetic operations of add, subtract, and multiply are + * bit-wise identical if the two operands are regarded as both + * being signed or both being unsigned. Therefore separate {@code + * addUnsigned}, etc. methods are not provided. + * + * @param dividend the value to be divided + * @param divisor the value doing the dividing + * @return the unsigned quotient of the first argument divided by + * the second argument + * @see #remainderUnsigned + * @since 1.8 + * + * @see Integer#divideUnsigned(int, int) + */ + public static @Unsigned byte divideUnsigned(@Unsigned byte dividend, @Unsigned byte divisor) { + return (byte) (Byte.toUnsignedInt(dividend) / Byte.toUnsignedInt(divisor)); + } + + /** + * Convenience method that stands in for the missing method {@code Short#divideUnsigned(short, short)}.
+ * This method was inspired by {@link Integer#divideUnsigned(int, int) the method of the same in Integer}
+ *
+ * Description copied from {@link Integer#divideUnsigned(int, int)}
+ * Returns the unsigned quotient of dividing the first argument by + * the second where each argument and the result is interpreted as + * an unsigned value. + * + *

Note that in two's complement arithmetic, the three other + * basic arithmetic operations of add, subtract, and multiply are + * bit-wise identical if the two operands are regarded as both + * being signed or both being unsigned. Therefore separate {@code + * addUnsigned}, etc. methods are not provided. + * + * @param dividend the value to be divided + * @param divisor the value doing the dividing + * @return the unsigned quotient of the first argument divided by + * the second argument + * @see #remainderUnsigned + * @since 1.8 + * + * @see Integer#divideUnsigned(int, int) + */ + public static @Unsigned short divideUnsigned(@Unsigned short dividend, @Unsigned short divisor) { + return (short) (Short.toUnsignedInt(dividend) / Short.toUnsignedInt(divisor)); + } + + /** + * Convenience method that delegates to {@link Integer#divideUnsigned(int, int)}.
+ *
+ * Description copied from {@link Integer#divideUnsigned(int, int)}
+ * Returns the unsigned quotient of dividing the first argument by + * the second where each argument and the result is interpreted as + * an unsigned value. + * + *

Note that in two's complement arithmetic, the three other + * basic arithmetic operations of add, subtract, and multiply are + * bit-wise identical if the two operands are regarded as both + * being signed or both being unsigned. Therefore separate {@code + * addUnsigned}, etc. methods are not provided. + * + * @param dividend the value to be divided + * @param divisor the value doing the dividing + * @return the unsigned quotient of the first argument divided by + * the second argument + * @see #remainderUnsigned + * @since 1.8 + * + * @see Integer#divideUnsigned(int, int) + */ + public static @Unsigned int divideUnsigned(@Unsigned int dividend, @Unsigned int divisor) { + return Integer.divideUnsigned(dividend, divisor); + } + + /** + * Convenience method that delegates to {@link Long#divideUnsigned(long, long)}.
+ *
+ * Description copied from {@link Long#divideUnsigned(long, long)}
+ * Returns the unsigned quotient of dividing the first argument by + * the second where each argument and the result is interpreted as + * an unsigned value. + * + *

Note that in two's complement arithmetic, the three other + * basic arithmetic operations of add, subtract, and multiply are + * bit-wise identical if the two operands are regarded as both + * being signed or both being unsigned. Therefore separate {@code + * addUnsigned}, etc. methods are not provided. + * + * @param dividend the value to be divided + * @param divisor the value doing the dividing + * @return the unsigned quotient of the first argument divided by + * the second argument + * @see #remainderUnsigned + * @since 1.8 + * + * @see Long#divideUnsigned(long, long) + */ + public static @Unsigned long divideUnsigned(@Unsigned long dividend, @Unsigned long divisor) { + return Long.divideUnsigned(dividend, divisor); + } + + public static @Unsigned byte remainderUnsigned(@Unsigned byte dividend, @Unsigned byte divisor) { + return (byte) (Byte.toUnsignedInt(dividend) % Byte.toUnsignedInt(divisor)); + } + + public static @Unsigned short remainderUnsigned(@Unsigned short dividend, @Unsigned short divisor) { + return (short) (Short.toUnsignedInt(dividend) % Short.toUnsignedInt(divisor)); + } + + public static @Unsigned int remainderUnsigned(@Unsigned int dividend, @Unsigned int divisor) { + return Integer.remainderUnsigned(dividend, divisor); + } + + public static @Unsigned long remainderUnsigned(@Unsigned long dividend, @Unsigned long divisor) { + return Long.remainderUnsigned(dividend, divisor); + } + + public static int compareUnsigned(byte x, byte y) { + return Byte.compareUnsigned(x, y); + } + + public static int compareUnsigned(short x, short y) { + return Short.compareUnsigned(x, y); + } + + public static int compareUnsigned(int x, int y) { + return Integer.compareUnsigned(x, y); + } + + public static int compareUnsigned(long x, long y) { + return Long.compareUnsigned(x, y); + } } \ No newline at end of file From 236347d571cab031b63ad916d4d906bc998d289f Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Thu, 25 Jun 2020 15:27:39 +0200 Subject: [PATCH 12/16] Added methods for unsigned conversion between integer types Added methods for unsigned conversion between integer types --- .../dev/dokan/dokan_java/UnsignedNumbers.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/main/java/dev/dokan/dokan_java/UnsignedNumbers.java b/src/main/java/dev/dokan/dokan_java/UnsignedNumbers.java index 1489eca..8b3093d 100644 --- a/src/main/java/dev/dokan/dokan_java/UnsignedNumbers.java +++ b/src/main/java/dev/dokan/dokan_java/UnsignedNumbers.java @@ -160,4 +160,64 @@ public static int compareUnsigned(int x, int y) { public static int compareUnsigned(long x, long y) { return Long.compareUnsigned(x, y); } + + ///////////////////////////////////////////////////////// + ////////////////////// Conversions ////////////////////// + ///////////////////////////////////////////////////////// + + ////////////////////// byte ////////////////////// + + public static @Unsigned byte toUnsignedByte(@Unsigned short value) { + return (byte) value; + } + + public static @Unsigned byte toUnsignedByte(@Unsigned int value) { + return (byte) value; + } + + public static @Unsigned byte toUnsignedByte(@Unsigned long value) { + return (byte) value; + } + + ////////////////////// short ////////////////////// + + public static @Unsigned short toUnsignedShort(@Unsigned byte value) { + return (short) (((short) value) & BYTE_MASK); + } + + public static @Unsigned short toUnsignedShort(@Unsigned int value) { + return (short) value; + } + + public static @Unsigned short toUnsignedShort(@Unsigned long value) { + return (short) value; + } + + ////////////////////// int ////////////////////// + + public static @Unsigned int toUnsignedInt(@Unsigned byte value) { + return ((int) value) & BYTE_MASK; + } + + public static @Unsigned int toUnsignedInt(@Unsigned short value) { + return ((int) value) & SHORT_MASK; + } + + public static @Unsigned int toUnsignedInt(@Unsigned long value) { + return (int) value; + } + + ////////////////////// long ////////////////////// + + public static @Unsigned long toUnsignedLong(@Unsigned byte value) { + return ((long) value) & BYTE_MASK; + } + + public static @Unsigned long toUnsignedLong(@Unsigned short value) { + return ((long) value) & SHORT_MASK; + } + + public static @Unsigned long toUnsignedLong(@Unsigned int value) { + return ((long) value) & INT_MASK; + } } \ No newline at end of file From bacc29306bd73312cf7cb46f8bb67e8e8247d7c2 Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Sat, 27 Jun 2020 22:51:17 +0200 Subject: [PATCH 13/16] Added missing constants from last commit to UnsignedNumbers Added constants that were missing from my last commit to UnsignedNumbers to allow compilation/fix compiler errors. Sorry! --- src/main/java/dev/dokan/dokan_java/UnsignedNumbers.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/dev/dokan/dokan_java/UnsignedNumbers.java b/src/main/java/dev/dokan/dokan_java/UnsignedNumbers.java index 8b3093d..19b28a0 100644 --- a/src/main/java/dev/dokan/dokan_java/UnsignedNumbers.java +++ b/src/main/java/dev/dokan/dokan_java/UnsignedNumbers.java @@ -3,6 +3,10 @@ public class UnsignedNumbers { + private static final short BYTE_MASK = 0xff; + private static final int SHORT_MASK = 0xffff; + private static final long INT_MASK = 0xffffff; + public static String toUnsignedString(@Unsigned byte value) { return toUnsignedString(Byte.toUnsignedInt(value)); } From a1eb826d0043c47eadb49c122c221253687ce2ce Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Sun, 28 Jun 2020 19:38:52 +0200 Subject: [PATCH 14/16] Changed @ Unsiged to only use TYPE_PARAMETER and TYPE_USE as targets Removed all unnecessary values from the @ Targets of @ Unsigned --- src/main/java/dev/dokan/dokan_java/Unsigned.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/dev/dokan/dokan_java/Unsigned.java b/src/main/java/dev/dokan/dokan_java/Unsigned.java index b85a358..eb06160 100644 --- a/src/main/java/dev/dokan/dokan_java/Unsigned.java +++ b/src/main/java/dev/dokan/dokan_java/Unsigned.java @@ -6,12 +6,15 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.ElementType.TYPE_PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; @Documented @Retention(RetentionPolicy.RUNTIME) -@Target(value = {METHOD, FIELD, PARAMETER, LOCAL_VARIABLE, TYPE_PARAMETER, TYPE_USE}) +//In Theory TYPE_USE should contain TYPE_PARAMETER, +//but the documentation is so vague that I'm really not sure to be honest +@Target(value = {TYPE_PARAMETER, TYPE_USE}) public @interface Unsigned { } From 75e582ca89b3651123aa3cd58fd64c5cc88456c3 Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Sun, 28 Jun 2020 21:03:31 +0200 Subject: [PATCH 15/16] Fixed typos in package dev.dokan.dokan_java.structure.* See: https://github.com/dokan-dev/dokan-java/pull/48#discussion_r446683030 --- .../dokan/dokan_java/structure/DokanAccessState.java | 2 +- .../dev/dokan/dokan_java/structure/DokanControl.java | 2 +- .../structure/filesecurity/AccessControlList.java | 4 ++-- .../filesecurity/SelfRelativeSecurityDescriptor.java | 10 +++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/dev/dokan/dokan_java/structure/DokanAccessState.java b/src/main/java/dev/dokan/dokan_java/structure/DokanAccessState.java index cc74f25..374a3a7 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/DokanAccessState.java +++ b/src/main/java/dev/dokan/dokan_java/structure/DokanAccessState.java @@ -46,7 +46,7 @@ public class DokanAccessState extends Structure { * A driver can check for the traverse access flag (TOKEN_HAS_TRAVERSE_PRIVILEGE). * For more information about how to check for traverse access, see Check for Traverse Privilege on IRP_MJ_CREATE. * A driver can also check for the TOKEN_IS_RESTRICTED flag. - * These flags are defined in Ntifs.h. + * These flags are defined in ntifs.h. */ @Unsigned public int Flags; diff --git a/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java b/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java index 2af2b11..97cc687 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java +++ b/src/main/java/dev/dokan/dokan_java/structure/DokanControl.java @@ -77,7 +77,7 @@ protected List getFieldOrder() { } /** - * Creates a java {@link List} of {@link DokanControl} strcutures given the pointer returned by NativeMethods#DokanGetMountPointList(boolean, LongByReference).
+ * Creates a java {@link List} of {@link DokanControl} structures given the pointer returned by NativeMethods#DokanGetMountPointList(boolean, LongByReference).
*
* Implementation note:
* Length is an unsigned 32-bit int. Java only supports arrays and lists up to an index size of 231-1 ({@link Integer#MAX_VALUE Integer.MAX_VALUE}). diff --git a/src/main/java/dev/dokan/dokan_java/structure/filesecurity/AccessControlList.java b/src/main/java/dev/dokan/dokan_java/structure/filesecurity/AccessControlList.java index e5f39c3..ef3e8e3 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/filesecurity/AccessControlList.java +++ b/src/main/java/dev/dokan/dokan_java/structure/filesecurity/AccessControlList.java @@ -8,7 +8,7 @@ import java.util.List; /** - * Objectoriented implementation of the ACL-structure used in a {@link SelfRelativeSecurityDescriptor}. + * Object-oriented implementation of the ACL-structure used in a {@link SelfRelativeSecurityDescriptor}. * For more information, please read the official Microsoft documentation. */ public class AccessControlList implements Byteable { @@ -55,7 +55,7 @@ private enum ACLType { private final short sbz2 = 0; /** - * List of AccessControlEntrys in this ACL + * List of AccessControlEntries in this ACL */ private List aces; diff --git a/src/main/java/dev/dokan/dokan_java/structure/filesecurity/SelfRelativeSecurityDescriptor.java b/src/main/java/dev/dokan/dokan_java/structure/filesecurity/SelfRelativeSecurityDescriptor.java index 86d822c..060476e 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/filesecurity/SelfRelativeSecurityDescriptor.java +++ b/src/main/java/dev/dokan/dokan_java/structure/filesecurity/SelfRelativeSecurityDescriptor.java @@ -72,7 +72,7 @@ public class SelfRelativeSecurityDescriptor implements Byteable { * Sacl * The SACL of the object. The length of the SID MUST be a multiple of 4. This field MUST be present if the SP flag is set. *

- * This implementation guarantees the existence of a SACL if SP-flag is set by only writing the flag if this strucutre is present. + * This implementation guarantees the existence of a SACL if SP-flag is set by only writing the flag if this structure is present. */ private Optional sacl; @@ -80,12 +80,12 @@ public class SelfRelativeSecurityDescriptor implements Byteable { * Dacl * The DACL of the object. The length of the SID MUST be a multiple of 4. This field MUST be present if the DP flag is set. *

- * This implementation guarantees the existence of a DACL if DP-flag is set by only writing the flag if this strucutre is present. + * This implementation guarantees the existence of a DACL if DP-flag is set by only writing the flag if this structure is present. */ private Optional dacl; /** - * Creates an empty SecurtiyDescriptor. + * Creates an empty SecurityDescriptor. * * @param control */ @@ -160,8 +160,8 @@ public byte[] toByteArray() { @Override public int sizeOfByteArray() { return 2 // the first fixed bytes (revision and sbz1) - + 2 // the 16bit big control mask - + 4 * 4 // the 4 32bit integer offset values indicating the offset to the following varaible length data fields + + 2 // the 16bit big control mask + + 4 * 4 // the 4 32bit integer offset values indicating the offset to the following variable length data fields + ownerSid.map(SecurityIdentifier::sizeOfByteArray).orElse(0) + groupSid.map(SecurityIdentifier::sizeOfByteArray).orElse(0) + sacl.map(AccessControlList::sizeOfByteArray).orElse(0) From b9aa49a902581c5ad01f01aeed07096d889973b0 Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Tue, 30 Jun 2020 21:51:25 +0200 Subject: [PATCH 16/16] Added JavaDoc for @ Unsigned See: https://github.com/dokan-dev/dokan-java/pull/48#discussion_r443747818 --- .../java/dev/dokan/dokan_java/Unsigned.java | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) diff --git a/src/main/java/dev/dokan/dokan_java/Unsigned.java b/src/main/java/dev/dokan/dokan_java/Unsigned.java index eb06160..20b3776 100644 --- a/src/main/java/dev/dokan/dokan_java/Unsigned.java +++ b/src/main/java/dev/dokan/dokan_java/Unsigned.java @@ -10,6 +10,182 @@ import static java.lang.annotation.ElementType.TYPE_USE; +/** + * This annotation is used used to indicate that a value with an integer type or an integer type pointer refers to an unsigned value.
+ * In this case integers are numbers without positions after decimal point. + * (Java's Default integer types being {@code byte, short, int, long} and their corresponding wrappers.) + *

Introduction

+ * Java stores integer types in Two's complement-Representation. + * Usually numbers represented as Two's complement use the Most-Significant-Bit (MSB) to store the sign of the number. + * If a field is annotated with @Unsigned this rule does not apply! + * Instead the MSB should be considered as part of the number itself, resulting in a doubled storage capacity of the field + * while removing the support for signed values (the field becomes unsigned and the value should only be interpreted as positive.) + * See this table as reference for the resulting differences when interpreting numbers: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Signed value (Java Default)Unsigned values (@Unsigned)Bitmask (for a byte)
000000 0000
110000 0001
220000 0010
64640100 0000
-1281281000 0000
-1271291000 0001
-12551111 1111
+ *

Usage

+ * As Java only supports signed numbers, the correct interpretation and handling of unsigned numbers must be dealt with by the developer. + * Because of this it's strongly recommended to tag all values that are unsigned as such by using this annotation.
+ * @Unsigned is defined to {@link Target target} {@link java.lang.annotation.ElementType#TYPE_PARAMETER} and + * {@link java.lang.annotation.ElementType#TYPE_USE} and can therefore be applied to all usages of types (type contexts) + * and parameterized types respectively. See §9.6.4.1 + * and §4.11 of "The Java® Language Specification" for + * further reference.
+ *
+ * At least the following cases ("minimum usage") should be annotated with @Unsigned to + * guarantee the best quality of code: + *
    + *
  • Field declarations: {@code @Unsigned private final int index;} + *
  • Local variable declarations: {@code @Unsigned int i;} + *
  • Parameters: {@code public void remove(@Unsigned int index) {...}} + *
  • Method return types: {@code public @Unsigned int getIndex() {...}} + *

+ * Additionally it's recommended to annotate any other usage of unsigned types ("advanced usage"), + * especially (but not limited to): + *
    + *
  • Parameterized types: {@code List<@Unsigned Integer> indices = new ArrayList<>();} + *
  • Casts: {@code Integer i = (@Unsigned Integer) uInt;} + *
  • Type declarations: {@code public class @Unsigned UInt {...}} + *
  • Extension/Implementation of unsigned types: {@code public class //@Unsigned// Index extends @Unsigned UInt {...}} + *
+ * Note: Contributions to the dokan-java project must annotate all usages of unsigned types (Minimum usage and advanced usage) + * with @Unsigned. + *

Pitfalls

+ * If a field is annotated with @Unsigned developers should take extra care handling it as some unsafe operations may lead + * to unexpected results. + * An operation is considered safe if using it with unsigned values yields the same results as using it with signed numbers. + * ("An operation is safe if it can be used the same way when dealing with unsigned values as one would use it with signed numbers.") + * The following (inconclusive) table shows which operations are safe or unsafe and how operations can be dealt with alternatively. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
OperationSafe/UnsafeRecommended course of action
Adding (+)Safe
Subtracting (-)Safe
Multiplication (*)Safe
Division (/)Unsafe{@link UnsignedNumbers#divideUnsigned(int, int)}
Remainder/ModuloUnsafe{@link UnsignedNumbers#remainderUnsigned(int, int)}
Comparision (%)Unsafe{@link UnsignedNumbers#compareUnsigned(int, int)}
PrintingUnsafe{@link UnsignedNumbers#toUnsignedString(int)}
DowncastingSafe{@link UnsignedNumbers#toUnsignedInt(long)}
UpcastingUnsafe{@link UnsignedNumbers#toUnsignedInt(short)}
+ * Note: Operations that require two numbers (e.g. addition, subtraction) should never be used with a signed + * and an unsigned number as arguments, even if the operation is usually considered safe. + * Doing so can result in heavy computational errors, as seen here:
+ *
+ * {@code //Don't do this:}
+ * {@code byte a = 64; //0100 0000}
+ * {@code @Unsigned byte b = 64; //0100 0000}
+ * {@code byte c = a + b; //0100 0000 + 0100 0000}
+ * {@code //--> c = -128; //1000 0000}
+ *
+ * This is a problem for the following reason: Before the addition the status (signed/unsigned) of + * a and b didn't matter (in fact they were equal). As soon as the value exceeds 127 the developer needs to decide + * whether c is signed or unsigned.
+ * If c is unsigned all future users of c must take care that they + * interpret it correctly. Also a comes from a signed context and could be negative. + * If a's MSB is set to indicate a negative value any computation that considers a unsigned must be wrong + * as it would interpret a as a big positive number instead of a negative number.
+ * If c is interpreted as signed, the computation is plain wrong (64 + 64 is not -128) because it leads to a number-overflow.
+ *
+ * {@code //Instead do this:}
+ * {@code byte a = 64;}
+ * {@code @Unsigned byte b = 64;}
+ * {@code short s = UnsignedNumbers.toUnsignedShort(b);}
+ * {@code short c = s + a;}
+ * {@code //--> c = 128;}
+ * + * @author JaniruTEC + * @see UnsignedNumbers + * @since 2.0 + */ @Documented @Retention(RetentionPolicy.RUNTIME) //In Theory TYPE_USE should contain TYPE_PARAMETER,