-
Notifications
You must be signed in to change notification settings - Fork 180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Async Create Request/Response with Oplock implementation. #354
base: master
Are you sure you want to change the base?
Implement Async Create Request/Response with Oplock implementation. #354
Conversation
Add Oplock related messages and enum classes. Add oplock related NtStatus. Read messageId in messageConverter to determine oplock message type. Implement equals and hashCode for SMB2FileId to let user easier to compare. Implement the Async Create Request/Response and Oplock Break Notification by implementing notificationHandler with SMBEvent. Capturing the messageId of async create before send and capturing the Oplock Break Notification and Create Response on receiving in Connection and delivery them to user with ensuring the sequence is exactly the same as TCP layer. Add more create API to achieve the condition mentioned before. Public some methods to allow the user to create the diskEntry by his/her own.
…rService for notifyHandler in SmbConfig. Add taskQueue (from Vert.x Library) for DiskShare notifyHandler. The Apache-2.0 from Vert.x had been kept and the modified code had added comment. Allow user pass in executorService for notifyHandler in SmbConfig instead of always create a new executorService for each DiskShare. This can avoid having multiple thread pool when multiple DiskShare is opened.
…or oplock break classes. Add SMB2OplockBreakFactory to create oplock break notification and acknowledgement. Lease break related should also using this factory to create. Keep the implementation details in the factory and simplify the messageConverter. Add super class for all SMB2_OPLOCK_BREAK(0x12) command classes to reduce the code duplication.
…fication type. Change NotificationHandler to have single handle method for each Notification type. Adopt the changes in DiskShare. Remove the enum class for notification type as no longer used. Add abstract class for notificationHandler. This abstract class is for user not interested for all notification and can simply extends the abstract class to override for some notification.
…cense in taskQueue class to fit CI requirement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not clear to me why the TaskQueue is needed. In this particular usage pattern it seems like submitting the Runnables directly to the Executor achieves the same thing. TaskQueue supports serialising execution of Runnables across multiple Executors, but that's not being used here as far as I can tell.
Hi @hierynomus, The |
Hi @pepijnve, Indeed the SingleThreadExecutor can provide the similar effect. If you are considering there are only one DiskShare instance exists, it make no difference. The difference between
Use single thread pool for each DiskShareEasy to implement and easy to guarantee the event sequence. But creating thread is an expensive operation operation, which should avoid. Also, having dedicated thread for each DiskShare is obviously over killed. The context switching overhead is high as well.Use a single thread pool for whole SmbjEasy to implement and easy to guarantee the event sequence. However, using single thread may become performance bottomneck as the asynchronous event scale up when numbers of DiskShare increase. Also, I had planned to add more and more asynchronous event in smbj (e.g. Notify, Lease), using single thread for whole Smbj may become performance bottomneck.Use a task queue per DiskShare and having multi-threaded thread pool for whole SmbjThe user is flexible to determine how many threads they needed. Multi-threading across different DiskShare instance is allowed.ConclusionAs a result, I think using a task queue per DiskShare and having multi-threaded thread pool for whole Smbj is the best choice. |
/** | ||
* [MS-SMB2].pdf 2.2.23 SMB2 OPLOCK_BREAK Notification - OplockLevel | ||
*/ | ||
public enum SMB2OplockBreakLevel implements EnumWithValue<SMB2OplockBreakLevel> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's merge this weith the SMB2OplockLevel, they're exactly the same values. I don't see any additional benefit to separating this Enum
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is because the valid set is different between requesting oplock and breaking oplock. Only LEVEL_NONE
and LEVEL_II
are valid for breaking oplock. Base on my understanding, each enum class should only contain the valid values (and also null in Java). Adding valid check
functions maybe is an alternative but valid check
function check on run time. Separating this enum will force the checking on compile time. In my opinion, error checking on compile time is better than on run time. For me, I think simply separate this enum should make the code looks simpler than calling valid check
function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please model it according to spec, there is no SMB2_OPLOCK_BREAK_LEVEL
, only SMB2_OPLOCK_LEVEL
. With different enums we need to have translation code in place to transform one into the other. Please merge them.
super(); | ||
} | ||
|
||
protected SMB2OplockBreak(int structureSize, SMB2Dialect dialect, SMB2MessageCommandCode messageType, long sessionId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the SMB2MessageCommandCode
from the signature, it is always SMB2_OPLOCK_BREAK
for this subtree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, will change it.
|
||
import java.util.Arrays; | ||
|
||
public class SMB2OplockBreakFactory{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
space before {
buffer.skip(24); | ||
byte[] messageId = buffer.readRawBytes(8); | ||
buffer.rpos(0); | ||
final boolean isBreakNotification = Arrays.equals(messageId, (new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make the byte[]
a constant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will add private static final long breakMessageId = -1;
on SMB2OplockBreakFactory and check by final boolean isBreakNotification = messageId == breakMessageId;
instead of using byte array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
// final int structureSize = buffer.readUInt16(); | ||
// buffer.rpos(0); | ||
|
||
if(isBreakNotification) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
spacing please :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess no spacing issue on this line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Look at the other places, a space between if
and (
is missing... (Here and in other places).
import java.util.concurrent.TimeUnit; | ||
import java.util.concurrent.TimeoutException; | ||
|
||
public class FutureWrapper<T extends U, U> implements Future<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this FutureWrapper here? What does it add?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, FutureWrapper is used to deal with issue on Connection publishing AsyncCreateResponseNotification
. In Java, Dogs
is a child of Animal
but List<Dogs>
is not a child of List<Animal>
as well as Future<Dogs>
is not a child of Future<Animal>
.
However, I just read the details for
// [MS-SMB2].pdf 3.2.5.1.8 Processing the Response
outstandingRequests.receivedResponseFor(messageId).getPromise().deliver(packet);
and just finds that it only delivering the packet to Future and do nothings more.
I was thinking there are some process is required to handle for 3.2.5.1.8 Processing the Response
so I just kept it in Future
format and get the Future
just as the synchronize create.
I think I should simply publish the SMB2CreateResponse
on the Connection and remove this FutureWrapper
class.
@@ -0,0 +1,120 @@ | |||
/* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's not copy-paste this. The license is not correct now anymore (wrong attribution).
Why aren't we using a simple LinkedBlockingQueue
to queue the events?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see your point. Let me check how to implement a "TaskQueue" with a LinkedBlockingQueue
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @hierynomus,
I guess same issue happening here. The implementation had already changed on latest commit. The line we commenting is just a comment in the code so GitHub didn't mark this change request is outdated. Could you please check the implementation?
private void oplockBreakNotification(final OplockBreakNotification oplockBreakNotification) { | ||
try { | ||
|
||
final SMB2FileId fileId = oplockBreakNotification.getFileId(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we check here whether the OplockBreakNotification.fileId
is actually handled by this DiskShare
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to check for fileId. The client MUST ignore it if fileId
not found.
Base on document,
3.2.5.1.2 Finding the Application Request for This Response
The client MUST locate the request for which this response was sent in reply by locating the request in
Connection.OutstandingRequests using the MessageId field of the SMB2 header. If the request is
not found, the response MUST be discarded as invalid.
If the MessageId is 0xFFFFFFFFFFFFFFFF, this is not a reply to a previous request, and the client
MUST NOT attempt to locate the request, but instead process it as follows:
If the command field in the SMB2 header is SMB2 OPLOCK_BREAK, it MUST be processed as specified
in 3.2.5.19. Otherwise, the response MUST be discarded as invalid.
3.2.5.19.1 Receiving an Oplock Break Notification
The client MUST locate the open in the Session.OpenTable using the FileId in the Oplock Break
Notification following the SMB2 header. If the open is not found, the oplock break indication MUST be
discarded, and no further processing is required.
And currently, it seems the DiskShare
didn't record for the openedHandle. I don't think we should add it just because an unnecessary checking.
But indeed reminds me something. The fileId
is only unique within a session
. We should check the oplockBreakNotification.getHeader().getSessionId()
match DiskShare.session.getSessionId()
or not before notifying the user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this moment multiple DiskShare
s will be receiving the message from the bus. You need to check which one needs to process the message, so you do need to check both the sessionId
as well as the fileId
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to be sure, is it sufficient to confirm the DiskShare
is same by checking the oplockBreakNotification.getHeader().getTreeId()
match DiskShare.treeConnect.getTreeId()
?
Update
Checking for `treeId` and `sessionId` should works for async create request/response. However, It seems Windows server would simply set both `treeId` and `sessionId` to `0` with messageId `-1` for `OplockBreakNotification`.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, that makes that we need to use a contextual bus per connection. Because it could very well be that the same fileId exists on multiple different connections (to different hosts).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @hierynomus,
The latest commit already using a contextual bus per connection. The fix is on commit a48ad30. I guess this line we are commenting didn't change at all. So, the GitHub didn't mark this change request is outdated automatically. Could you please check the implementation?
}, notifyExecutor); | ||
}else { | ||
logger.warn("FileId {}, NotificationHandler not exist to handle Oplock Break.", fileId); | ||
throw new IllegalStateException("NotificationHandler not exist to handle Oplock Break."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this an exception, and the createRequestNotification
does not throw an exception?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the createRequestNotification
and createResponseNotification
is always called even you are using synchronize create
(the existing create
). The is a valid use case for a user to using the synchronize create
and not set the NotificationHandler (as this is intended to handle async event only).
However, it is not a valid use case for using any level of oplock higher than None
on any kind of create
command. Because the OplockBreakNotification
is always "Async" (or I should say Server initiated?).
Codecov Report
@@ Coverage Diff @@
## master #354 +/- ##
============================================
- Coverage 45.79% 45.25% -0.54%
- Complexity 870 892 +22
============================================
Files 217 231 +14
Lines 6457 6697 +240
Branches 471 486 +15
============================================
+ Hits 2957 3031 +74
- Misses 3257 3417 +160
- Partials 243 249 +6
Continue to review full report at Codecov.
|
/** | ||
* [MS-SMB2].pdf 2.2.23 SMB2 OPLOCK_BREAK Notification - OplockLevel | ||
*/ | ||
public enum SMB2OplockBreakLevel implements EnumWithValue<SMB2OplockBreakLevel> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please model it according to spec, there is no SMB2_OPLOCK_BREAK_LEVEL
, only SMB2_OPLOCK_LEVEL
. With different enums we need to have translation code in place to transform one into the other. Please merge them.
…on. Return SMB2CreateResponse instead of Future in asyncNotification. Minor Fixes. Make breakMessageId constant in SMB2OplockBreakFactory. Directly return SMB2CreateResponse instead of Future in AsyncCreateResponseNotification. Remove FutureWrapper class. Fix equals not implemented correctly in SMB2FileId. Fix didn't check should handle or not when the DiskShare received a async notification. Didn't simply check the treeId in asyncCreateResponseNotification is because the Server may response an "Asynchronous Responses". At that case, treeId is not exist in header.
…n19941005/smbj into implement-async-create-oplock
Hi @hierynomus, The commit I just pushed should fixed some issue. The remaining outstanding issue : TODO List:
Please let me know if the list missed some issue you had mentioned before. For issue 6 and 7, I guess while I complete issue 7 and |
…ance smb2oplock classes. Enhance notification classes. Re-implement taskQueue. Fix publishing connection internal event through client global EventBus. Separating the client global EventBus and connection internal EventBus and only passing the connection internal EventBus to all the classes under connection (e.g. session). Enhance SMB2OplockBreak class constructor to reduce code. Enhance notification classes. The Async request notification would use sessionId and treeId to check the DiskShare is required to handle while the Async response notification would use messageId to check the DiskShare is required to handle. Add internal notification for notifying all the messageId of the supported AsyncOperation on the DiskShare. Re-implement the taskQueue class. Update test case to adopt changes.
…cutorService in config.
Base on SMB2 document, there are no SMB2_OPLOCK_BREAK_LEVEL. Merge to SMB2_OPLOCK_LEVEL and remove SMB2_OPLOCK_BREAK_LEVEL.
Hi @hierynomus, I guess I had already change the code to fit all your change request. For |
Can you take a look at the codacy messages? There are a few degradations in quality. |
No problem. Will try to fix the codacy stuffs. |
Hi @hierynomus, The codacy issue had been fixed. However I still got error:
I guess this is caused by this branch is out of date? |
Add Oplock related messages and enum classes. Add oplock related NtStatus. Read messageId in messageConverter to determine oplock message type. Implement equals and hashCode for SMB2FileId to let user easier to compare. Implement the Async Create Request/Response and Oplock Break Notification by implementing notificationHandler with SMBEvent. Capturing the messageId of async create before send and capturing the Oplock Break Notification and Create Response on receiving in Connection and delivery them to user with ensuring the sequence is exactly the same as TCP layer. Add more create API to achieve the condition mentioned before. Public some methods to allow the user to create the diskEntry by his/her own.
Add taskQueue and test case for Async Create and oplock related implementation.