From 20a98d24592886eedaf1efba203f730680e8cc0f Mon Sep 17 00:00:00 2001 From: yin19941005 Date: Wed, 4 Jul 2018 18:26:29 +0800 Subject: [PATCH 01/12] Implement Async Create Request/Response with Oplock implementation. 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. --- .../java/com/hierynomus/mserref/NtStatus.java | 1 + .../com/hierynomus/mssmb2/SMB2FileId.java | 23 ++ .../mssmb2/SMB2OplockBreakLevel.java | 37 +++ .../hierynomus/mssmb2/SMB2OplockLevel.java | 42 +++ .../mssmb2/messages/SMB2CreateRequest.java | 9 +- .../mssmb2/messages/SMB2CreateResponse.java | 22 +- .../mssmb2/messages/SMB2MessageConverter.java | 16 +- .../SMB2OplockBreakAcknowledgment.java | 47 ++++ ...SMB2OplockBreakAcknowledgmentResponse.java | 50 ++++ .../messages/SMB2OplockBreakNotification.java | 49 ++++ .../commons/concurrent/FutureWrapper.java | 58 ++++ .../smbj/connection/Connection.java | 50 ++++ .../event/AsyncCreateRequestNotification.java | 40 +++ .../AsyncCreateResponseNotification.java | 49 ++++ .../smbj/event/AsyncNotification.java | 22 ++ .../smbj/event/OplockBreakNotification.java | 41 +++ .../smbj/event/handler/MessageIdCallback.java | 20 ++ .../event/handler/NotificationHandler.java | 23 ++ .../handler/NotificationMessageType.java | 25 ++ .../com/hierynomus/smbj/session/Session.java | 17 +- .../com/hierynomus/smbj/share/DiskEntry.java | 20 ++ .../com/hierynomus/smbj/share/DiskShare.java | 264 +++++++++++++++++- .../com/hierynomus/smbj/share/PipeShare.java | 12 +- .../hierynomus/smbj/share/PrinterShare.java | 1 + .../java/com/hierynomus/smbj/share/Share.java | 39 ++- 25 files changed, 955 insertions(+), 22 deletions(-) create mode 100644 src/main/java/com/hierynomus/mssmb2/SMB2OplockBreakLevel.java create mode 100644 src/main/java/com/hierynomus/mssmb2/SMB2OplockLevel.java create mode 100644 src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgment.java create mode 100644 src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgmentResponse.java create mode 100644 src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakNotification.java create mode 100644 src/main/java/com/hierynomus/protocol/commons/concurrent/FutureWrapper.java create mode 100644 src/main/java/com/hierynomus/smbj/event/AsyncCreateRequestNotification.java create mode 100644 src/main/java/com/hierynomus/smbj/event/AsyncCreateResponseNotification.java create mode 100644 src/main/java/com/hierynomus/smbj/event/AsyncNotification.java create mode 100644 src/main/java/com/hierynomus/smbj/event/OplockBreakNotification.java create mode 100644 src/main/java/com/hierynomus/smbj/event/handler/MessageIdCallback.java create mode 100644 src/main/java/com/hierynomus/smbj/event/handler/NotificationHandler.java create mode 100644 src/main/java/com/hierynomus/smbj/event/handler/NotificationMessageType.java diff --git a/src/main/java/com/hierynomus/mserref/NtStatus.java b/src/main/java/com/hierynomus/mserref/NtStatus.java index fd769328..50843495 100644 --- a/src/main/java/com/hierynomus/mserref/NtStatus.java +++ b/src/main/java/com/hierynomus/mserref/NtStatus.java @@ -65,6 +65,7 @@ public enum NtStatus implements EnumWithValue { STATUS_NOT_SAME_DEVICE(0xC00000D4L), STATUS_FILE_RENAMED(0xC00000D5L), STATUS_OPLOCK_NOT_GRANTED(0xC00000E2L), + STATUS_INVALID_OPLOCK_PROTOCOL(0xC00000E3L), STATUS_INTERNAL_ERROR(0xC00000E5L), STATUS_UNEXPECTED_IO_ERROR(0xC00000E9L), STATUS_DIRECTORY_NOT_EMPTY(0xC0000101L), diff --git a/src/main/java/com/hierynomus/mssmb2/SMB2FileId.java b/src/main/java/com/hierynomus/mssmb2/SMB2FileId.java index fbd105a2..872d86b6 100644 --- a/src/main/java/com/hierynomus/mssmb2/SMB2FileId.java +++ b/src/main/java/com/hierynomus/mssmb2/SMB2FileId.java @@ -16,9 +16,12 @@ package com.hierynomus.mssmb2; import com.hierynomus.protocol.commons.ByteArrayUtils; +import com.hierynomus.protocol.commons.Objects; import com.hierynomus.protocol.commons.buffer.Buffer; import com.hierynomus.smb.SMBBuffer; +import java.util.Arrays; + /** * [MS-SMB2].pdf 2.2.14.1 SMB2_FILEID */ @@ -53,4 +56,24 @@ public String toString() { "persistentHandle=" + ByteArrayUtils.printHex(persistentHandle) + '}'; } + + public String toHexString() { + return ByteArrayUtils.toHex(persistentHandle) + ByteArrayUtils.toHex(volatileHandle); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SMB2FileId smb2FileId = (SMB2FileId) o; + return Objects.equals(persistentHandle, smb2FileId.persistentHandle) && + Objects.equals(volatileHandle, smb2FileId.volatileHandle); + } + + @Override + public int hashCode() { + int result = Arrays.hashCode(persistentHandle); + result = 31 * result + Arrays.hashCode(volatileHandle); + return result; + } } diff --git a/src/main/java/com/hierynomus/mssmb2/SMB2OplockBreakLevel.java b/src/main/java/com/hierynomus/mssmb2/SMB2OplockBreakLevel.java new file mode 100644 index 00000000..3262c2cc --- /dev/null +++ b/src/main/java/com/hierynomus/mssmb2/SMB2OplockBreakLevel.java @@ -0,0 +1,37 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.mssmb2; + +import com.hierynomus.protocol.commons.EnumWithValue; + +/** + * [MS-SMB2].pdf 2.2.23 SMB2 OPLOCK_BREAK Notification - OplockLevel + */ +public enum SMB2OplockBreakLevel implements EnumWithValue { + SMB2_OPLOCK_LEVEL_NONE(0x00L), + SMB2_OPLOCK_LEVEL_II(0x01L); + + private long value; + + SMB2OplockBreakLevel(long value) { + this.value = value; + } + + @Override + public long getValue() { + return value; + } +} diff --git a/src/main/java/com/hierynomus/mssmb2/SMB2OplockLevel.java b/src/main/java/com/hierynomus/mssmb2/SMB2OplockLevel.java new file mode 100644 index 00000000..8349ab43 --- /dev/null +++ b/src/main/java/com/hierynomus/mssmb2/SMB2OplockLevel.java @@ -0,0 +1,42 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.mssmb2; + +import com.hierynomus.protocol.commons.EnumWithValue; + +/** + * [MS-SMB2].pdf 2.2.13 SMB2 CREATE Request - OplockLevel + *

+ */ +public enum SMB2OplockLevel implements EnumWithValue { + SMB2_OPLOCK_LEVEL_NONE(0x00L), + SMB2_OPLOCK_LEVEL_II(0x01L), + SMB2_OPLOCK_LEVEL_EXCLUSIVE(0x08L), + SMB2_OPLOCK_LEVEL_BATCH(0x09L), + // TODO: implement and support using lease + OPLOCK_LEVEL_LEASE(0xFFL); + + private long value; + + SMB2OplockLevel(long value) { + this.value = value; + } + + @Override + public long getValue() { + return value; + } +} diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2CreateRequest.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2CreateRequest.java index f3170c11..f2544947 100644 --- a/src/main/java/com/hierynomus/mssmb2/messages/SMB2CreateRequest.java +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2CreateRequest.java @@ -39,16 +39,19 @@ public class SMB2CreateRequest extends SMB2Packet { private final SmbPath path; private final Set accessMask; private final SMB2ImpersonationLevel impersonationLevel; + private final SMB2OplockLevel oplockLevel; @SuppressWarnings("PMD.ExcessiveParameterList") public SMB2CreateRequest(SMB2Dialect smbDialect, long sessionId, long treeId, + SMB2OplockLevel oplockLevel, SMB2ImpersonationLevel impersonationLevel, Set accessMask, Set fileAttributes, Set shareAccess, SMB2CreateDisposition createDisposition, Set createOptions, SmbPath path) { super(57, smbDialect, SMB2MessageCommandCode.SMB2_CREATE, sessionId, treeId); + this.oplockLevel = ensureNotNull(oplockLevel, SMB2OplockLevel.SMB2_OPLOCK_LEVEL_NONE); this.impersonationLevel = ensureNotNull(impersonationLevel, SMB2ImpersonationLevel.Identification); this.accessMask = accessMask; this.fileAttributes = ensureNotNull(fileAttributes, FileAttributes.class); @@ -62,7 +65,7 @@ public SMB2CreateRequest(SMB2Dialect smbDialect, protected void writeTo(SMBBuffer buffer) { buffer.putUInt16(structureSize); // StructureSize (2 bytes) buffer.putByte((byte) 0); // SecurityFlags (1 byte) - Reserved - buffer.putByte((byte) 0); // RequestedOpLockLevel (1 byte) - None + buffer.putByte((byte)oplockLevel.getValue()); // RequestedOpLockLevel (1 byte) buffer.putUInt32(impersonationLevel.getValue()); // ImpersonationLevel (4 bytes) - Identification buffer.putReserved(8); // SmbCreateFlags (8 bytes) buffer.putReserved(8); // Reserved (8 bytes) @@ -95,4 +98,8 @@ protected void writeTo(SMBBuffer buffer) { buffer.putRawBytes(nameBytes); } + + public SmbPath getPath() { + return path; + } } diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2CreateResponse.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2CreateResponse.java index 274f20b0..407e6914 100644 --- a/src/main/java/com/hierynomus/mssmb2/messages/SMB2CreateResponse.java +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2CreateResponse.java @@ -20,6 +20,7 @@ import com.hierynomus.msfscc.FileAttributes; import com.hierynomus.mssmb2.SMB2CreateAction; import com.hierynomus.mssmb2.SMB2FileId; +import com.hierynomus.mssmb2.SMB2OplockLevel; import com.hierynomus.mssmb2.SMB2Packet; import com.hierynomus.protocol.commons.EnumWithValue; import com.hierynomus.protocol.commons.buffer.Buffer; @@ -34,26 +35,29 @@ */ public class SMB2CreateResponse extends SMB2Packet { + private SMB2OplockLevel oplockLevel; private SMB2CreateAction createAction; private FileTime creationTime; private FileTime lastAccessTime; private FileTime lastWriteTime; private FileTime changeTime; + private long allocationSize; + private long endOfFile; private Set fileAttributes; private SMB2FileId fileId; @Override protected void readMessage(SMBBuffer buffer) throws Buffer.BufferException { buffer.readUInt16(); // StructureSize (2 bytes) - buffer.readByte(); // OpLockLevel (1 byte) - Not used yet + oplockLevel = EnumWithValue.EnumUtils.valueOf(buffer.readByte(), SMB2OplockLevel.class, SMB2OplockLevel.SMB2_OPLOCK_LEVEL_NONE); // OpLockLevel (1 byte) buffer.readByte(); // Flags (1 byte) - Only for 3.x else Reserved createAction = EnumWithValue.EnumUtils.valueOf(buffer.readUInt32(), SMB2CreateAction.class, null); // CreateAction (4 bytes) creationTime = MsDataTypes.readFileTime(buffer); // CreationTime (8 bytes) lastAccessTime = MsDataTypes.readFileTime(buffer); // LastAccessTime (8 bytes) lastWriteTime = MsDataTypes.readFileTime(buffer); // LastWriteTime (8 bytes) changeTime = MsDataTypes.readFileTime(buffer); // ChangeTime (8 bytes) - buffer.readRawBytes(8); // AllocationSize (8 bytes) - Ignore - buffer.readRawBytes(8); // EndOfFile (8 bytes) + allocationSize = buffer.readLong(); // AllocationSize (8 bytes) + endOfFile = buffer.readUInt64(); // EndOfFile (8 bytes) fileAttributes = toEnumSet(buffer.readUInt32(), FileAttributes.class); // FileAttributes (4 bytes) buffer.skip(4); // Reserved2 (4 bytes) fileId = SMB2FileId.read(buffer); // FileId (16 bytes) @@ -63,6 +67,10 @@ protected void readMessage(SMBBuffer buffer) throws Buffer.BufferException { buffer.readUInt32();// CreateContextsLength (4 bytes) } + public SMB2OplockLevel getOplockLevel() { + return oplockLevel; + } + public SMB2CreateAction getCreateAction() { return createAction; } @@ -83,6 +91,14 @@ public FileTime getChangeTime() { return changeTime; } + public long getAllocationSize() { + return allocationSize; + } + + public long getEndOfFile() { + return endOfFile; + } + public Set getFileAttributes() { return fileAttributes; } diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2MessageConverter.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2MessageConverter.java index 42a0abd7..fdaa8b7e 100644 --- a/src/main/java/com/hierynomus/mssmb2/messages/SMB2MessageConverter.java +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2MessageConverter.java @@ -23,6 +23,8 @@ import com.hierynomus.smbj.common.Check; import com.hierynomus.smbj.common.SMBRuntimeException; +import java.util.Arrays; + public class SMB2MessageConverter implements PacketFactory { private SMB2Packet read(SMBBuffer buffer) throws Buffer.BufferException { @@ -66,9 +68,21 @@ private SMB2Packet read(SMBBuffer buffer) throws Buffer.BufferException { return read(new SMB2QueryInfoResponse(), buffer); case SMB2_SET_INFO: return read(new SMB2SetInfoResponse(), buffer); + case SMB2_OPLOCK_BREAK: + // 3.2.5.19 Receiving an SMB2 OPLOCK_BREAK Notification + // If the MessageId field of the SMB2 header of the response is 0xFFFFFFFFFFFFFFFF, + // this MUST be + // processed as an oplock break indication. + buffer.skip(24); + byte[] messageId = buffer.readRawBytes(8); + buffer.rpos(0); + if(Arrays.equals(messageId, (new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}))) { + return read(new SMB2OplockBreakNotification(), buffer); + } else { + return read(new SMB2OplockBreakAcknowledgmentResponse(), buffer); + } case SMB2_LOCK: case SMB2_CANCEL: - case SMB2_OPLOCK_BREAK: default: throw new SMBRuntimeException("Unknown SMB2 Message Command type: " + command); diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgment.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgment.java new file mode 100644 index 00000000..461b2f4c --- /dev/null +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgment.java @@ -0,0 +1,47 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.mssmb2.messages; + +import com.hierynomus.mssmb2.SMB2Dialect; +import com.hierynomus.mssmb2.SMB2FileId; +import com.hierynomus.mssmb2.SMB2MessageCommandCode; +import com.hierynomus.mssmb2.SMB2OplockBreakLevel; +import com.hierynomus.mssmb2.SMB2Packet; +import com.hierynomus.smb.SMBBuffer; + +/** + * [MS-SMB2].pdf 2.2.24 SMB2 OPLOCK_BREAK Acknowledgment + */ +public class SMB2OplockBreakAcknowledgment extends SMB2Packet { + + private SMB2OplockBreakLevel oplockLevel; + private SMB2FileId fileId; + + public SMB2OplockBreakAcknowledgment(SMB2Dialect negotiatedDialect, long sessionId, long treeId, SMB2OplockBreakLevel oplockLevel, SMB2FileId fileId) { + super(24, negotiatedDialect, SMB2MessageCommandCode.SMB2_OPLOCK_BREAK, sessionId, treeId); + this.oplockLevel = oplockLevel; + this.fileId = fileId; + } + + @Override + protected void writeTo(SMBBuffer buffer) { + buffer.putUInt16(structureSize); // StructureSize (2 bytes) + buffer.putByte((byte)oplockLevel.getValue()); // OpLockLevel (1 byte) + buffer.putReserved1(); // Reserved (1 bytes) + buffer.putReserved4(); // Reserved (4 bytes) + fileId.write(buffer); // FileId (16 bytes) + } +} diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgmentResponse.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgmentResponse.java new file mode 100644 index 00000000..8a0b5a6d --- /dev/null +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgmentResponse.java @@ -0,0 +1,50 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.mssmb2.messages; + +import com.hierynomus.mssmb2.SMB2FileId; +import com.hierynomus.mssmb2.SMB2OplockBreakLevel; +import com.hierynomus.mssmb2.SMB2Packet; +import com.hierynomus.protocol.commons.EnumWithValue; +import com.hierynomus.protocol.commons.buffer.Buffer; +import com.hierynomus.smb.SMBBuffer; + +/*** + * [MS-SMB2].pdf 2.2.25 SMB2 OPLOCK_BREAK Response + */ +public class SMB2OplockBreakAcknowledgmentResponse extends SMB2Packet { + + private SMB2OplockBreakLevel oplockLevel; + private SMB2FileId fileId; + + @Override + protected void readMessage(SMBBuffer buffer) throws Buffer.BufferException { + buffer.readUInt16(); // StructureSize (2 bytes) + oplockLevel = EnumWithValue.EnumUtils.valueOf(buffer.readByte(), SMB2OplockBreakLevel.class, SMB2OplockBreakLevel.SMB2_OPLOCK_LEVEL_NONE); // OpLockLevel (1 byte) + buffer.readByte(); // Reserved (1 byte) + buffer.skip(4); // Reserved2 (4 bytes) + fileId = SMB2FileId.read(buffer); // FileId (16 bytes) + } + + public SMB2OplockBreakLevel getOplockLevel() { + return oplockLevel; + } + + public SMB2FileId getFileId() { + return fileId; + } + +} diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakNotification.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakNotification.java new file mode 100644 index 00000000..8d6d19ff --- /dev/null +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakNotification.java @@ -0,0 +1,49 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.mssmb2.messages; + +import com.hierynomus.mssmb2.SMB2FileId; +import com.hierynomus.mssmb2.SMB2OplockBreakLevel; +import com.hierynomus.mssmb2.SMB2Packet; +import com.hierynomus.protocol.commons.EnumWithValue; +import com.hierynomus.protocol.commons.buffer.Buffer; +import com.hierynomus.smb.SMBBuffer; + +/** + * [MS-SMB2].pdf 2.2.23 SMB2 OPLOCK_BREAK Notification + */ +public class SMB2OplockBreakNotification extends SMB2Packet { + + private SMB2OplockBreakLevel oplockLevel; + private SMB2FileId fileId; + + @Override + protected void readMessage(SMBBuffer buffer) throws Buffer.BufferException { + buffer.readUInt16(); // StructureSize (2 bytes) + oplockLevel = EnumWithValue.EnumUtils.valueOf(buffer.readByte(), SMB2OplockBreakLevel.class, SMB2OplockBreakLevel.SMB2_OPLOCK_LEVEL_NONE); // OpLockLevel (1 byte) + buffer.readByte(); // Reserved (1 byte) + buffer.skip(4); // Reserved2 (4 bytes) + fileId = SMB2FileId.read(buffer); // FileId (16 bytes) + } + + public SMB2OplockBreakLevel getOplockLevel() { + return oplockLevel; + } + + public SMB2FileId getFileId() { + return fileId; + } +} diff --git a/src/main/java/com/hierynomus/protocol/commons/concurrent/FutureWrapper.java b/src/main/java/com/hierynomus/protocol/commons/concurrent/FutureWrapper.java new file mode 100644 index 00000000..1a98a279 --- /dev/null +++ b/src/main/java/com/hierynomus/protocol/commons/concurrent/FutureWrapper.java @@ -0,0 +1,58 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.protocol.commons.concurrent; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class FutureWrapper implements Future { + + private final Future mDelegate; + + public FutureWrapper(Future delegate) { + mDelegate = delegate; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return mDelegate.cancel(mayInterruptIfRunning); + } + + @Override + public boolean isCancelled() { + return mDelegate.isCancelled(); + } + + @Override + public boolean isDone() { + return mDelegate.isDone(); + } + + @Override + public T get() throws InterruptedException, ExecutionException { + //noinspection unchecked + return (T)mDelegate.get(); + } + + @Override + public T get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + //noinspection unchecked + return (T)mDelegate.get(timeout, unit); + } +} diff --git a/src/main/java/com/hierynomus/smbj/connection/Connection.java b/src/main/java/com/hierynomus/smbj/connection/Connection.java index ff122afa..6421c0d5 100644 --- a/src/main/java/com/hierynomus/smbj/connection/Connection.java +++ b/src/main/java/com/hierynomus/smbj/connection/Connection.java @@ -25,6 +25,7 @@ import com.hierynomus.protocol.commons.Factory; import com.hierynomus.protocol.commons.buffer.Buffer; import com.hierynomus.protocol.commons.concurrent.CancellableFuture; +import com.hierynomus.protocol.commons.concurrent.FutureWrapper; import com.hierynomus.protocol.commons.concurrent.Futures; import com.hierynomus.protocol.transport.*; import com.hierynomus.smb.SMBPacket; @@ -34,9 +35,14 @@ import com.hierynomus.smbj.auth.AuthenticationContext; import com.hierynomus.smbj.auth.Authenticator; import com.hierynomus.smbj.common.SMBRuntimeException; +import com.hierynomus.smbj.common.SmbPath; +import com.hierynomus.smbj.event.AsyncCreateRequestNotification; +import com.hierynomus.smbj.event.AsyncCreateResponseNotification; import com.hierynomus.smbj.event.ConnectionClosed; +import com.hierynomus.smbj.event.OplockBreakNotification; import com.hierynomus.smbj.event.SMBEventBus; import com.hierynomus.smbj.event.SessionLoggedOff; +import com.hierynomus.smbj.event.handler.MessageIdCallback; import com.hierynomus.smbj.session.Session; import com.hierynomus.spnego.NegTokenInit; import com.hierynomus.spnego.SpnegoException; @@ -255,6 +261,19 @@ private Authenticator getAuthenticator(AuthenticationContext context) throws IOE * @throws TransportException When a transport level error occurred */ public Future send(SMB2Packet packet) throws TransportException { + return send(packet, null); + } + + /*** + * Send a packet and callback for the corresponding messageId. Currently, only support SMB2CreateRequest + * + * @param packet SMBPacket to send + * @param messageIdCallback callback to return corresponding messageId + * @return a Future to be used to retrieve the response packet + * @throws TransportException When a transport level error occurred + */ + public Future send(SMB2Packet packet, MessageIdCallback messageIdCallback) throws TransportException { + lock.lock(); try { int availableCredits = sequenceWindow.available(); @@ -267,6 +286,18 @@ public Future send(SMB2Packet packet) throws Transport logger.debug("Granted {} (out of {}) credits to {}", grantCredits, availableCredits, packet); packet.getHeader().setCreditRequest(Math.max(SequenceWindow.PREFERRED_MINIMUM_CREDITS - availableCredits - grantCredits, grantCredits)); + long messageId = packet.getHeader().getMessageId(); + + if(messageIdCallback != null) { + messageIdCallback.callback(messageId); + } + + if(packet instanceof SMB2CreateRequest) { + SMB2CreateRequest createRequest = (SMB2CreateRequest)packet; + SmbPath path = createRequest.getPath(); + bus.publish(new AsyncCreateRequestNotification(messageId, path)); + } + Request request = new Request(packet.getHeader().getMessageId(), UUID.randomUUID()); outstandingRequests.registerOutstanding(request); transport.write(packet); @@ -370,6 +401,18 @@ public void handle(SMBPacket uncheckedPacket) throws TransportException { long messageId = packet.getSequenceNumber(); if (!outstandingRequests.isOutstanding(messageId)) { + + // 3.2.5.19 Receiving an SMB2 OPLOCK_BREAK Notification + // If the MessageId field of the SMB2 header of the response is 0xFFFFFFFFFFFFFFFF, + // this MUST be + // processed as an oplock break indication. + if(packet instanceof SMB2OplockBreakNotification) { + SMB2OplockBreakNotification oplockBreakNotification = (SMB2OplockBreakNotification)packet; + logger.debug("Received SMB2OplockBreakNotification Packet for FileId {} with {}", oplockBreakNotification.getFileId(), oplockBreakNotification.getOplockLevel()); + bus.publish(new OplockBreakNotification(oplockBreakNotification.getOplockLevel(), oplockBreakNotification.getFileId())); + return; + } + throw new TransportException("Received response with unknown sequence number <<" + messageId + ">>"); } @@ -410,6 +453,13 @@ public void handle(SMBPacket uncheckedPacket) throws TransportException { verifyPacketSignature(packet, session); } + // Handing case for Oplock/Lease related issue + if(packet instanceof SMB2CreateResponse) { + SMB2CreateResponse smb2CreateResponse = (SMB2CreateResponse)packet; + Future future = new FutureWrapper<>(outstandingRequests.getRequestByMessageId(messageId).getPromise().future()); + bus.publish(new AsyncCreateResponseNotification(messageId, smb2CreateResponse.getFileId(), future)); + } + // [MS-SMB2].pdf 3.2.5.1.8 Processing the Response outstandingRequests.receivedResponseFor(messageId).getPromise().deliver(packet); } diff --git a/src/main/java/com/hierynomus/smbj/event/AsyncCreateRequestNotification.java b/src/main/java/com/hierynomus/smbj/event/AsyncCreateRequestNotification.java new file mode 100644 index 00000000..338f5262 --- /dev/null +++ b/src/main/java/com/hierynomus/smbj/event/AsyncCreateRequestNotification.java @@ -0,0 +1,40 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.smbj.event; + +import com.hierynomus.smbj.common.SmbPath; + +/*** + * Event for notifying the SmbPath to DiskShare Notification Handler + */ +public class AsyncCreateRequestNotification implements SMBEvent, AsyncNotification { + + private long messageId; + private SmbPath path; + + public AsyncCreateRequestNotification(long messageId, SmbPath path) { + this.messageId = messageId; + this.path = path; + } + + public long getMessageId() { + return messageId; + } + + public SmbPath getPath() { + return path; + } +} diff --git a/src/main/java/com/hierynomus/smbj/event/AsyncCreateResponseNotification.java b/src/main/java/com/hierynomus/smbj/event/AsyncCreateResponseNotification.java new file mode 100644 index 00000000..97acfa6a --- /dev/null +++ b/src/main/java/com/hierynomus/smbj/event/AsyncCreateResponseNotification.java @@ -0,0 +1,49 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.smbj.event; + +import com.hierynomus.mssmb2.SMB2FileId; +import com.hierynomus.mssmb2.messages.SMB2CreateResponse; + +import java.util.concurrent.Future; + +/*** + * Event for notifying the fileId and CreateResponseFuture to corresponding messageId on AysncCreate + */ +public class AsyncCreateResponseNotification implements SMBEvent, AsyncNotification { + + private long messageId; + private SMB2FileId fileId; + private Future future; + + public AsyncCreateResponseNotification(long messageId, SMB2FileId fileId, Future future) { + this.messageId = messageId; + this.fileId = fileId; + this.future = future; + } + + public long getMessageId() { + return messageId; + } + + public SMB2FileId getFileId() { + return fileId; + } + + public Future getFuture() { + return future; + } +} diff --git a/src/main/java/com/hierynomus/smbj/event/AsyncNotification.java b/src/main/java/com/hierynomus/smbj/event/AsyncNotification.java new file mode 100644 index 00000000..45b6984a --- /dev/null +++ b/src/main/java/com/hierynomus/smbj/event/AsyncNotification.java @@ -0,0 +1,22 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.smbj.event; + +/** + * Base class for asynchronous notification events that need to be handled by notification handlers (observers) + */ +public interface AsyncNotification { +} diff --git a/src/main/java/com/hierynomus/smbj/event/OplockBreakNotification.java b/src/main/java/com/hierynomus/smbj/event/OplockBreakNotification.java new file mode 100644 index 00000000..b4009238 --- /dev/null +++ b/src/main/java/com/hierynomus/smbj/event/OplockBreakNotification.java @@ -0,0 +1,41 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.smbj.event; + +import com.hierynomus.mssmb2.SMB2FileId; +import com.hierynomus.mssmb2.SMB2OplockBreakLevel; + +/*** + * Event for notifying the oplock break notification for corresponding fileId + */ +public class OplockBreakNotification implements SMBEvent, AsyncNotification { + + private SMB2OplockBreakLevel oplockLevel; + private SMB2FileId fileId; + + public OplockBreakNotification(SMB2OplockBreakLevel oplockLevel, SMB2FileId fileId) { + this.oplockLevel = oplockLevel; + this.fileId = fileId; + } + + public SMB2OplockBreakLevel getOplockLevel() { + return oplockLevel; + } + + public SMB2FileId getFileId() { + return fileId; + } +} diff --git a/src/main/java/com/hierynomus/smbj/event/handler/MessageIdCallback.java b/src/main/java/com/hierynomus/smbj/event/handler/MessageIdCallback.java new file mode 100644 index 00000000..c227e543 --- /dev/null +++ b/src/main/java/com/hierynomus/smbj/event/handler/MessageIdCallback.java @@ -0,0 +1,20 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.smbj.event.handler; + +public interface MessageIdCallback { + void callback(long messageId); +} diff --git a/src/main/java/com/hierynomus/smbj/event/handler/NotificationHandler.java b/src/main/java/com/hierynomus/smbj/event/handler/NotificationHandler.java new file mode 100644 index 00000000..02de56b0 --- /dev/null +++ b/src/main/java/com/hierynomus/smbj/event/handler/NotificationHandler.java @@ -0,0 +1,23 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.smbj.event.handler; + +import com.hierynomus.smbj.event.AsyncNotification; + +public interface NotificationHandler { + void handle(NotificationMessageType type, + AsyncNotification asyncNotification); +} diff --git a/src/main/java/com/hierynomus/smbj/event/handler/NotificationMessageType.java b/src/main/java/com/hierynomus/smbj/event/handler/NotificationMessageType.java new file mode 100644 index 00000000..8d8423ac --- /dev/null +++ b/src/main/java/com/hierynomus/smbj/event/handler/NotificationMessageType.java @@ -0,0 +1,25 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.smbj.event.handler; + +/*** + * The enum class for determine which type of message is sending to NotificationHandler + */ +public enum NotificationMessageType { + SMB2_CREATE_REQUEST, + SMB2_CREATE_RESPONSE, + SMB2_OPLOCK_BREAK_NOTIFICATION +} diff --git a/src/main/java/com/hierynomus/smbj/session/Session.java b/src/main/java/com/hierynomus/smbj/session/Session.java index e2cc033e..22c60711 100644 --- a/src/main/java/com/hierynomus/smbj/session/Session.java +++ b/src/main/java/com/hierynomus/smbj/session/Session.java @@ -29,6 +29,7 @@ import com.hierynomus.smbj.event.SMBEventBus; import com.hierynomus.smbj.event.SessionLoggedOff; import com.hierynomus.smbj.event.TreeDisconnected; +import com.hierynomus.smbj.event.handler.MessageIdCallback; import com.hierynomus.smbj.paths.PathResolveException; import com.hierynomus.smbj.paths.PathResolver; import com.hierynomus.smbj.share.*; @@ -180,7 +181,7 @@ private Share connectTree(final String shareName) { Share share; if (response.isDiskShare()) { - share = new DiskShare(smbPath, treeConnect, pathResolver); + share = new DiskShare(smbPath, treeConnect, pathResolver, bus); } else if (response.isNamedPipe()) { share = new PipeShare(smbPath, treeConnect); } else if (response.isPrinterShare()) { @@ -277,10 +278,22 @@ public Connection getConnection() { * @throws TransportException */ public Future send(SMB2Packet packet) throws TransportException { + return send(packet, null); + } + + /*** + * send a packet and callback for the corresponding messageId. The packet will be signed or not depending on the session's flags. + * + * @param packet SMBPacket to send + * @param messageIdCallback callback to return corresponding messageId + * @return a Future to be used to retrieve the response packet + * @throws TransportException + */ + public Future send(SMB2Packet packet, MessageIdCallback messageIdCallback) throws TransportException { if (signingRequired && !packetSignatory.isInitialized()) { throw new TransportException("Message signing is required, but no signing key is negotiated"); } - return connection.send(packetSignatory.sign(packet)); + return connection.send(packetSignatory.sign(packet), messageIdCallback); } public T processSendResponse(SMB2CreateRequest packet) throws TransportException { diff --git a/src/main/java/com/hierynomus/smbj/share/DiskEntry.java b/src/main/java/com/hierynomus/smbj/share/DiskEntry.java index 00948635..39a67c25 100644 --- a/src/main/java/com/hierynomus/smbj/share/DiskEntry.java +++ b/src/main/java/com/hierynomus/smbj/share/DiskEntry.java @@ -23,7 +23,9 @@ import com.hierynomus.msfscc.fileinformation.FileRenameInformation; import com.hierynomus.msfscc.fileinformation.FileSettableInformation; import com.hierynomus.mssmb2.SMB2FileId; +import com.hierynomus.mssmb2.SMB2OplockBreakLevel; import com.hierynomus.mssmb2.SMBApiException; +import com.hierynomus.mssmb2.messages.SMB2OplockBreakAcknowledgmentResponse; import com.hierynomus.protocol.transport.TransportException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,6 +51,14 @@ public void close() { share.closeFileId(fileId); } + public DiskShare getShare() { + return share; + } + + public String getFileName() { + return fileName; + } + public SMB2FileId getFileId() { return fileId; } @@ -181,4 +191,14 @@ public void closeSilently() { logger.warn("File close failed for {},{},{}", fileName, share, fileId, e); } } + + /*** + * Send a acknowledgment for Oplock Break Notification. 2.2.24 SMB2 OPLOCK_BREAK Acknowledgment. + * + * @param oplockLevel the oplock break level after receiving the oplock break notification (current holding oplock level) + * @return Server response to oplock break acknowledgment. 2.2.25 SMB2 OPLOCK_BREAK Response. + */ + public SMB2OplockBreakAcknowledgmentResponse acknowledgeOplockBreak(SMB2OplockBreakLevel oplockLevel) { + return share.sendOplockBreakAcknowledgment(fileId, oplockLevel); + } } diff --git a/src/main/java/com/hierynomus/smbj/share/DiskShare.java b/src/main/java/com/hierynomus/smbj/share/DiskShare.java index eee425ae..13bc71e7 100644 --- a/src/main/java/com/hierynomus/smbj/share/DiskShare.java +++ b/src/main/java/com/hierynomus/smbj/share/DiskShare.java @@ -35,14 +35,29 @@ import com.hierynomus.smbj.common.SMBRuntimeException; import com.hierynomus.smbj.common.SmbPath; import com.hierynomus.smbj.connection.Connection; +import com.hierynomus.smbj.event.AsyncCreateRequestNotification; +import com.hierynomus.smbj.event.AsyncCreateResponseNotification; +import com.hierynomus.smbj.event.OplockBreakNotification; +import com.hierynomus.smbj.event.SMBEventBus; +import com.hierynomus.smbj.event.handler.MessageIdCallback; +import com.hierynomus.smbj.event.handler.NotificationHandler; import com.hierynomus.smbj.paths.PathResolveException; import com.hierynomus.smbj.paths.PathResolver; import com.hierynomus.smbj.session.Session; +import net.engio.mbassy.listener.Handler; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.IOException; import java.util.EnumSet; import java.util.List; import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; import static com.hierynomus.msdtyp.AccessMask.*; import static com.hierynomus.mserref.NtStatus.*; @@ -54,21 +69,107 @@ import static com.hierynomus.mssmb2.SMB2CreateOptions.FILE_NON_DIRECTORY_FILE; import static com.hierynomus.mssmb2.SMB2ShareAccess.*; import static com.hierynomus.mssmb2.messages.SMB2QueryInfoRequest.SMB2QueryInfoType.SMB2_0_INFO_SECURITY; +import static com.hierynomus.smbj.event.handler.NotificationMessageType.SMB2_CREATE_REQUEST; +import static com.hierynomus.smbj.event.handler.NotificationMessageType.SMB2_CREATE_RESPONSE; +import static com.hierynomus.smbj.event.handler.NotificationMessageType.SMB2_OPLOCK_BREAK_NOTIFICATION; import static java.util.EnumSet.of; import static java.util.EnumSet.noneOf; public class DiskShare extends Share { + private static final Logger logger = LoggerFactory.getLogger(DiskShare.class); private final PathResolver resolver; + private SMBEventBus bus; + private NotificationHandler notificationHandler = null; + + // TODO: replace this with taskQueue and just having one notify thread pool on Smbj Library + private final ExecutorService notifyExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = Executors.defaultThreadFactory().newThread(r); + t.setDaemon(true); + t.setName(smbPath.getShareName() + "-Thread-1"); + return t; + } + }); - public DiskShare(SmbPath smbPath, TreeConnect treeConnect, PathResolver pathResolver) { + public DiskShare(SmbPath smbPath, TreeConnect treeConnect, PathResolver pathResolver, SMBEventBus bus) { super(smbPath, treeConnect); this.resolver = pathResolver; + this.bus = bus; + if (bus != null) { + bus.subscribe(this); + } + } + + @Override + public void close() throws IOException { + super.close(); + // cleanup for executor + notifyExecutor.shutdown(); } public DiskEntry open(String path, Set accessMask, Set attributes, Set shareAccesses, SMB2CreateDisposition createDisposition, Set createOptions) { + return open(path, null, null, accessMask, attributes, shareAccesses, createDisposition, createOptions); + } + + public DiskEntry open(String path, SMB2OplockLevel oplockLevel, SMB2ImpersonationLevel impersonationLevel, Set accessMask, Set attributes, Set shareAccesses, SMB2CreateDisposition createDisposition, Set createOptions) { + SMB2CreateResponseDiskEntry result = openWithResponse(path, oplockLevel, impersonationLevel, accessMask, attributes, shareAccesses, createDisposition, createOptions); + return result.getDiskEntry(); + } + + /*** + * Synchronously open a diskEntry. Returning the diskEntry with the createResponse. + * + * @param path target file path + * @param oplockLevel requesting oplock level + * @param impersonationLevel requesting impersonation level + * @param accessMask desired access + * @param attributes file attributes + * @param shareAccesses the share access of this create request + * @param createDisposition create disposition of this create request + * @param createOptions create options of this create request + * @return the diskEntry and the corresponding createResponse. + */ + public SMB2CreateResponseDiskEntry openWithResponse(String path, SMB2OplockLevel oplockLevel, SMB2ImpersonationLevel impersonationLevel, Set accessMask, Set attributes, Set shareAccesses, SMB2CreateDisposition createDisposition, Set createOptions) { SmbPath pathAndFile = new SmbPath(smbPath, path); - SMB2CreateResponseContext response = createFileAndResolve(pathAndFile, null, accessMask, attributes, shareAccesses, createDisposition, createOptions); - return getDiskEntry(path, response); + SMB2CreateResponseContext response = createFileAndResolve(pathAndFile, oplockLevel, impersonationLevel, accessMask, attributes, shareAccesses, createDisposition, createOptions); + return new SMB2CreateResponseDiskEntry(response.resp, getDiskEntry(path, response)); + } + + /*** + * Send a create request and return a Future for create response. User are required to deal with DFS issue by himself. + * + * @param path target file path + * @param oplockLevel requesting oplock level + * @param impersonationLevel requesting impersonation level + * @param accessMask desired access + * @param attributes file attributes + * @param shareAccesses the share access of this create request + * @param createDisposition create disposition of this create request + * @param createOptions create options of this create request + * @return a Future to be used to retrieve the create response packet + */ + public Future openAsync(String path, SMB2OplockLevel oplockLevel, SMB2ImpersonationLevel impersonationLevel, Set accessMask, Set attributes, Set shareAccesses, SMB2CreateDisposition createDisposition, Set createOptions) { + return openAsync(path, oplockLevel, impersonationLevel, accessMask, attributes, shareAccesses, createDisposition, createOptions, null); + } + + /*** + * Send a create request and callback for messageId for create response. User are required to deal with DFS issue by himself. + * + * @param path target file path + * @param oplockLevel requesting oplock level + * @param impersonationLevel requesting impersonation level + * @param accessMask desired access + * @param attributes file attributes + * @param shareAccesses the share access of this create request + * @param createDisposition create disposition of this create request + * @param createOptions create options of this create request + * @param messageIdCallback callback to return corresponding messageId + * @return a Future to be used to retrieve the create response packet + */ + public Future openAsync(String path, SMB2OplockLevel oplockLevel, SMB2ImpersonationLevel impersonationLevel, Set accessMask, Set attributes, Set shareAccesses, SMB2CreateDisposition createDisposition, Set createOptions, MessageIdCallback messageIdCallback) { + SmbPath pathAndFile = new SmbPath(smbPath, path); + return super.createAsync(pathAndFile, oplockLevel, impersonationLevel, accessMask, attributes, shareAccesses, createDisposition, createOptions, messageIdCallback); } @Override @@ -76,8 +177,8 @@ protected Set getCreateSuccessStatus() { return resolver.handledStates(); } - private SMB2CreateResponseContext createFileAndResolve(SmbPath path, SMB2ImpersonationLevel impersonationLevel, Set accessMask, Set fileAttributes, Set shareAccess, SMB2CreateDisposition createDisposition, Set createOptions) { - SMB2CreateResponse resp = super.createFile(path, impersonationLevel, accessMask, fileAttributes, shareAccess, createDisposition, createOptions); + private SMB2CreateResponseContext createFileAndResolve(SmbPath path, SMB2OplockLevel oplockLevel, SMB2ImpersonationLevel impersonationLevel, Set accessMask, Set fileAttributes, Set shareAccess, SMB2CreateDisposition createDisposition, Set createOptions) { + SMB2CreateResponse resp = super.createFile(path, oplockLevel, impersonationLevel, accessMask, fileAttributes, shareAccess, createDisposition, createOptions); try { SmbPath target = resolver.resolve(session, resp, path); DiskShare resolveShare = this; @@ -89,7 +190,7 @@ private SMB2CreateResponseContext createFileAndResolve(SmbPath path, SMB2Imperso resolveShare = (DiskShare) connectedSession.connectShare(target.getShareName()); } if (!path.equals(target)) { - return resolveShare.createFileAndResolve(target, impersonationLevel, accessMask, fileAttributes, shareAccess, createDisposition, createOptions); + return resolveShare.createFileAndResolve(target, oplockLevel, impersonationLevel, accessMask, fileAttributes, shareAccess, createDisposition, createOptions); } } catch (PathResolveException e) { throw new SMBApiException(e.getStatus(), SMB2MessageCommandCode.SMB2_CREATE, "Cannot resolve path " + path, e); @@ -106,7 +207,7 @@ private Session buildNewSession(SMB2CreateResponse resp, SmbPath target) { } } - protected DiskEntry getDiskEntry(String path, SMB2CreateResponseContext responseContext) { + public DiskEntry getDiskEntry(String path, SMB2CreateResponseContext responseContext) { SMB2CreateResponse response = responseContext.resp; if (response.getFileAttributes().contains(FILE_ATTRIBUTE_DIRECTORY)) { return new Directory(response.getFileId(), responseContext.share, path); @@ -119,6 +220,13 @@ protected DiskEntry getDiskEntry(String path, SMB2CreateResponseContext response * Get a handle to a directory in the given path */ public Directory openDirectory(String path, Set accessMask, Set attributes, Set shareAccesses, SMB2CreateDisposition createDisposition, Set createOptions) { + return openDirectory(path, null, null, accessMask, attributes, shareAccesses, createDisposition, createOptions); + } + + /** + * Get a handle to a directory in the given path + */ + public Directory openDirectory(String path, SMB2OplockLevel oplockLevel, SMB2ImpersonationLevel impersonationLevel, Set accessMask, Set attributes, Set shareAccesses, SMB2CreateDisposition createDisposition, Set createOptions) { EnumSet actualCreateOptions = createOptions != null ? EnumSet.copyOf(createOptions) : EnumSet.noneOf(SMB2CreateOptions.class); actualCreateOptions.add(FILE_DIRECTORY_FILE); actualCreateOptions.remove(FILE_NON_DIRECTORY_FILE); @@ -128,6 +236,8 @@ public Directory openDirectory(String path, Set accessMask, Set accessMask, Set accessMask, Set attributes, Set shareAccesses, SMB2CreateDisposition createDisposition, Set createOptions) { + return openFile(path, null, null, accessMask, attributes, shareAccesses, createDisposition, createOptions); + } + + /** + * Get a handle to a file in the given path + */ + public File openFile(String path, SMB2OplockLevel oplockLevel, SMB2ImpersonationLevel impersonationLevel, Set accessMask, Set attributes, Set shareAccesses, SMB2CreateDisposition createDisposition, Set createOptions) { EnumSet actualCreateOptions = createOptions != null ? EnumSet.copyOf(createOptions) : EnumSet.noneOf(SMB2CreateOptions.class); actualCreateOptions.add(FILE_NON_DIRECTORY_FILE); actualCreateOptions.remove(FILE_DIRECTORY_FILE); @@ -146,6 +266,8 @@ public File openFile(String path, Set accessMask, Set security ); } + /*** + * 3.2.5.19 Receiving an SMB2 OPLOCK_BREAK Notification, this handler is responsible to call acknowledgeOplockBreak if needed. Set the handler for Receiving an Oplock Break Notification. + * You MUST set this handler before create/open diskEntry with oplock. + * + * @param handler handler for Receiving an Oplock Break Notification and Async Create Request/Response. + */ + public void setNotificationHandler(NotificationHandler handler) { + this.notificationHandler = handler; + } + + /*** + * Handler for handing the oplock break notification event from server. 3.2.5.19 Receiving an SMB2 OPLOCK_BREAK Notification. + * + * @param oplockBreakNotification received oplock break notification from server. + */ + @Handler + @SuppressWarnings("unused") + private void oplockBreakNotification(final OplockBreakNotification oplockBreakNotification) { + try { + + final SMB2FileId fileId = oplockBreakNotification.getFileId(); + final SMB2OplockBreakLevel oplockLevel = oplockBreakNotification.getOplockLevel(); + logger.debug("FileId {} received OplockBreakNotification, Oplock level {}", fileId, oplockLevel); + + if(notificationHandler != null) { + // Preventing the improper use of handler (holding the thread). if holding thread, timeout exception will be throw. + notifyExecutor.submit(new Runnable() { + @Override + public void run() { + notificationHandler.handle(SMB2_OPLOCK_BREAK_NOTIFICATION, + oplockBreakNotification); + } + }); + }else { + logger.warn("FileId {}, NotificationHandler not exist to handle Oplock Break.", fileId); + throw new IllegalStateException("NotificationHandler not exist to handle Oplock Break."); + } + + } catch (Throwable t) { + logger.error("Handling oplockBreakNotification error occur : ", t); + throw t; + } + } + + /*** + * Async create request handler. + * + * @param asyncCreateRequestNotification filePath with the corresponding messageId. + */ + @Handler + @SuppressWarnings("unused") + private void createRequestNotification(final AsyncCreateRequestNotification asyncCreateRequestNotification) { + try { + if(notificationHandler != null) { + // Preventing the improper use of handler (holding the thread). if holding thread, timeout exception will be throw. + notifyExecutor.submit(new Runnable() { + @Override + public void run() { + notificationHandler.handle(SMB2_CREATE_REQUEST, + asyncCreateRequestNotification); + } + }); + }else { + logger.debug("NotificationHandler not exist to handle asyncCreateRequestNotification"); + } + } catch (Throwable t) { + logger.error("Handling createRequestNotification error occur : ", t); + throw t; + } + } + + /*** + * Async create response handler. This is also a oplock related handler. + * Passing the createResponse Future to the client. + * This is also intended to prevent oplock break too fast and not able to handle oplock break notification properly. + * Notify the client oplock is granted on createResponse but still under processing. + * + * @param asyncCreateResponseNotification the corresponding messageId and fileId with the Future of createResponse. + */ + @Handler + @SuppressWarnings("unused") + private void createResponseNotification(final AsyncCreateResponseNotification asyncCreateResponseNotification) { + try { + if(notificationHandler != null) { + // Preventing the improper use of handler (holding the thread). if holding thread, timeout exception will be throw. + notifyExecutor.submit(new Runnable() { + @Override + public void run() { + notificationHandler.handle(SMB2_CREATE_RESPONSE, + asyncCreateResponseNotification); + } + }); + }else { + logger.debug("NotificationHandler not exist to handle asyncCreateResponseNotification"); + } + } catch (Throwable t) { + logger.error("Handling createResponseNotification error occur : ", t); + throw t; + } + } + @Override public String toString() { return getClass().getSimpleName() + "[" + getSmbPath() + "]"; } /** - * A return object for the {@link #createFileAndResolve(SmbPath, SMB2ImpersonationLevel, Set, Set, Set, SMB2CreateDisposition, Set)} call. + * A return object for the {@link #createFileAndResolve(SmbPath, SMB2OplockLevel, SMB2ImpersonationLevel, Set, Set, Set, SMB2CreateDisposition, Set)} call. * * This object wraps the {@link SMB2CreateResponse} and the actual {@link Share} which generated it if the path needed to be resolved. */ - static class SMB2CreateResponseContext { + public static class SMB2CreateResponseContext { final SMB2CreateResponse resp; final DiskShare share; @@ -453,4 +676,27 @@ public SMB2CreateResponseContext(SMB2CreateResponse resp, DiskShare share) { this.share = share; } } + + /** + * A return object for the {@link #openWithResponse(String, SMB2OplockLevel, SMB2ImpersonationLevel, Set, Set, Set, SMB2CreateDisposition, Set)} call. + * + * This object wraps the {@link SMB2CreateResponse} and the diskEntry instance {@link DiskEntry}. + */ + public static class SMB2CreateResponseDiskEntry { + final SMB2CreateResponse resp; + final DiskEntry diskEntry; + + public SMB2CreateResponseDiskEntry(SMB2CreateResponse resp, DiskEntry diskEntry) { + this.resp = resp; + this.diskEntry = diskEntry; + } + + public SMB2CreateResponse getCreateResponse() { + return resp; + } + + public DiskEntry getDiskEntry() { + return diskEntry; + } + } } diff --git a/src/main/java/com/hierynomus/smbj/share/PipeShare.java b/src/main/java/com/hierynomus/smbj/share/PipeShare.java index 48d5bcea..72cf18cb 100644 --- a/src/main/java/com/hierynomus/smbj/share/PipeShare.java +++ b/src/main/java/com/hierynomus/smbj/share/PipeShare.java @@ -90,14 +90,22 @@ public boolean waitForPipe(String name, long timeout, TimeUnit timeoutUnit) { } public NamedPipe open(String name, SMB2ImpersonationLevel impersonationLevel, Set accessMask, Set attributes, Set shareAccesses, SMB2CreateDisposition createDisposition, Set createOptions) { + return open(name, null, impersonationLevel, accessMask, attributes, shareAccesses, createDisposition, createOptions); + } + + public NamedPipe open(String name, SMB2OplockLevel oplockLevel, SMB2ImpersonationLevel impersonationLevel, Set accessMask, Set attributes, Set shareAccesses, SMB2CreateDisposition createDisposition, Set createOptions) { SmbPath path = new SmbPath(smbPath, name); - SMB2FileId response = super.openFileId(path, impersonationLevel, accessMask, attributes, shareAccesses, createDisposition, createOptions); + SMB2FileId response = super.openFileId(path, oplockLevel, impersonationLevel, accessMask, attributes, shareAccesses, createDisposition, createOptions); return new NamedPipe(response, this, path); } public SMB2FileId openFileId(String name, SMB2ImpersonationLevel impersonationLevel, Set accessMask, Set fileAttributes, Set shareAccess, SMB2CreateDisposition createDisposition, Set createOptions) { + return openFileId(name, null, impersonationLevel, accessMask, fileAttributes, shareAccess, createDisposition, createOptions); + } + + public SMB2FileId openFileId(String name, SMB2OplockLevel oplockLevel, SMB2ImpersonationLevel impersonationLevel, Set accessMask, Set fileAttributes, Set shareAccess, SMB2CreateDisposition createDisposition, Set createOptions) { SmbPath path = new SmbPath(smbPath, name); - return super.openFileId(path, impersonationLevel, accessMask, fileAttributes, shareAccess, createDisposition, createOptions); + return super.openFileId(path, oplockLevel, impersonationLevel, accessMask, fileAttributes, shareAccess, createDisposition, createOptions); } public void closeFileId(SMB2FileId fileId) throws SMBApiException { diff --git a/src/main/java/com/hierynomus/smbj/share/PrinterShare.java b/src/main/java/com/hierynomus/smbj/share/PrinterShare.java index ad0a9592..6e968a37 100644 --- a/src/main/java/com/hierynomus/smbj/share/PrinterShare.java +++ b/src/main/java/com/hierynomus/smbj/share/PrinterShare.java @@ -49,6 +49,7 @@ public void print(ByteChunkProvider provider) { public void print(ByteChunkProvider provider, ProgressListener progressListener) { SMB2FileId fileId = openFileId(null, + null, null, EnumSet.of(AccessMask.FILE_WRITE_DATA), EnumSet.of(FileAttributes.FILE_ATTRIBUTE_NORMAL), diff --git a/src/main/java/com/hierynomus/smbj/share/Share.java b/src/main/java/com/hierynomus/smbj/share/Share.java index 8227b4a4..a2c33fc4 100644 --- a/src/main/java/com/hierynomus/smbj/share/Share.java +++ b/src/main/java/com/hierynomus/smbj/share/Share.java @@ -30,6 +30,7 @@ import com.hierynomus.smbj.common.SmbPath; import com.hierynomus.smbj.connection.Connection; import com.hierynomus.smbj.connection.NegotiatedProtocol; +import com.hierynomus.smbj.event.handler.MessageIdCallback; import com.hierynomus.smbj.io.ArrayByteChunkProvider; import com.hierynomus.smbj.io.ByteChunkProvider; import com.hierynomus.smbj.io.EmptyByteChunkProvider; @@ -116,14 +117,15 @@ int getWriteBufferSize() { return writeBufferSize; } - SMB2FileId openFileId(SmbPath path, SMB2ImpersonationLevel impersonationLevel, Set accessMask, Set fileAttributes, Set shareAccess, SMB2CreateDisposition createDisposition, Set createOptions) { - return createFile(path, impersonationLevel, accessMask, fileAttributes, shareAccess, createDisposition, createOptions).getFileId(); + SMB2FileId openFileId(SmbPath path, SMB2OplockLevel oplockLevel, SMB2ImpersonationLevel impersonationLevel, Set accessMask, Set fileAttributes, Set shareAccess, SMB2CreateDisposition createDisposition, Set createOptions) { + return createFile(path, oplockLevel, impersonationLevel, accessMask, fileAttributes, shareAccess, createDisposition, createOptions).getFileId(); } - SMB2CreateResponse createFile(SmbPath path, SMB2ImpersonationLevel impersonationLevel, Set accessMask, Set fileAttributes, Set shareAccess, SMB2CreateDisposition createDisposition, Set createOptions) { + SMB2CreateResponse createFile(SmbPath path, SMB2OplockLevel oplockLevel, SMB2ImpersonationLevel impersonationLevel, Set accessMask, Set fileAttributes, Set shareAccess, SMB2CreateDisposition createDisposition, Set createOptions) { SMB2CreateRequest cr = new SMB2CreateRequest( dialect, sessionId, treeId, + oplockLevel, impersonationLevel, accessMask, fileAttributes, @@ -136,6 +138,22 @@ SMB2CreateResponse createFile(SmbPath path, SMB2ImpersonationLevel impersonation return resp; } + Future createAsync(SmbPath path, SMB2OplockLevel oplockLevel, SMB2ImpersonationLevel impersonationLevel, Set accessMask, Set fileAttributes, Set shareAccess, SMB2CreateDisposition createDisposition, Set createOptions, MessageIdCallback messageIdCallback) { + SMB2CreateRequest cr = new SMB2CreateRequest( + dialect, + sessionId, treeId, + oplockLevel, + impersonationLevel, + accessMask, + fileAttributes, + shareAccess, + createDisposition, + createOptions, + path + ); + return send(cr, messageIdCallback); + } + protected Set getCreateSuccessStatus() { return SUCCESS_OR_SYMLINK; } @@ -317,18 +335,31 @@ private Future ioctlAsync(SMB2FileId fileId, long ctlCode, bo return send(ioreq); } + SMB2OplockBreakAcknowledgmentResponse sendOplockBreakAcknowledgment(SMB2FileId fileId, SMB2OplockBreakLevel oplockLevel) { + SMB2OplockBreakAcknowledgment qreq = new SMB2OplockBreakAcknowledgment( + dialect, + sessionId, treeId, + oplockLevel, fileId + ); + return sendReceive(qreq, "OplockBreakAck", fileId, SUCCESS, transactTimeout); + } + private T sendReceive(SMB2Packet request, String name, Object target, Set successResponses, long timeout) { Future fut = send(request); return receive(fut, name, target, successResponses, timeout); } private Future send(SMB2Packet request) { + return send(request, null); + } + + private Future send(SMB2Packet request, MessageIdCallback messageIdCallback) { if (!isConnected()) { throw new SMBRuntimeException(getClass().getSimpleName() + " has already been closed"); } try { - return session.send(request); + return session.send(request, messageIdCallback); } catch (TransportException e) { throw new SMBRuntimeException(e); } From eb92e52161e436e83cb29c055e8e2a47f1cbf3c8 Mon Sep 17 00:00:00 2001 From: yin19941005 Date: Thu, 5 Jul 2018 17:55:10 +0800 Subject: [PATCH 02/12] Add taskQueue for DiskShare notifyHandler. Allow user pass in executorService 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. --- .../commons/concurrent/TaskQueue.java | 120 ++++++++++++++++++ .../java/com/hierynomus/smbj/SmbConfig.java | 16 ++- .../com/hierynomus/smbj/share/DiskShare.java | 48 ++++--- 3 files changed, 165 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/hierynomus/protocol/commons/concurrent/TaskQueue.java diff --git a/src/main/java/com/hierynomus/protocol/commons/concurrent/TaskQueue.java b/src/main/java/com/hierynomus/protocol/commons/concurrent/TaskQueue.java new file mode 100644 index 00000000..60c55565 --- /dev/null +++ b/src/main/java/com/hierynomus/protocol/commons/concurrent/TaskQueue.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2011-2017 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// Modified: Changed the package +package com.hierynomus.protocol.commons.concurrent; + +// Modified: Changed Logger +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.LinkedList; +import java.util.concurrent.Executor; + +/** + * A task queue that always run all tasks in order. The executor to run the tasks is passed when + * the tasks when the tasks are executed, this executor is not guaranteed to be used, as if several + * tasks are queued, the original thread will be used. + * + * More specifically, any call B to the {@link #execute(Runnable, Executor)} method that happens-after another call A to the + * same method, will result in B's task running after A's. + * + * @author David Lloyd + * @author Tim Fox + * @author Julien Viet + */ +public class TaskQueue { + + // Modified: Changed Logger + static final Logger log = LoggerFactory.getLogger(TaskQueue.class); + + private static class Task { + + private final Runnable runnable; + private final Executor exec; + + public Task(Runnable runnable, Executor exec) { + this.runnable = runnable; + this.exec = exec; + } + } + + // @protectedby tasks + private final LinkedList tasks = new LinkedList<>(); + + // @protectedby tasks + private Executor current; + + private final Runnable runner; + + public TaskQueue() { + // Modified: Changed from Java 8 Lambda Sytle to Java 7 Style + runner = new Runnable() { + @Override + public void run() { + TaskQueue.this.run(); + } + }; + } + + private void run() { + for (; ; ) { + final Task task; + synchronized (tasks) { + task = tasks.poll(); + if (task == null) { + current = null; + return; + } + if (task.exec != current) { + tasks.addFirst(task); + task.exec.execute(runner); + current = task.exec; + return; + } + } + try { + task.runnable.run(); + } catch (Throwable t) { + log.error("Caught unexpected Throwable", t); + } + } + }; + + /** + * Run a task. + * + * @param task the task to run. + */ + public void execute(Runnable task, Executor executor) { + synchronized (tasks) { + tasks.add(new Task(task, executor)); + if (current == null) { + current = executor; + executor.execute(runner); + } + } + } +} diff --git a/src/main/java/com/hierynomus/smbj/SmbConfig.java b/src/main/java/com/hierynomus/smbj/SmbConfig.java index 3ba01127..1450a443 100644 --- a/src/main/java/com/hierynomus/smbj/SmbConfig.java +++ b/src/main/java/com/hierynomus/smbj/SmbConfig.java @@ -31,6 +31,7 @@ import javax.net.SocketFactory; import java.security.SecureRandom; import java.util.*; +import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; public final class SmbConfig { @@ -72,6 +73,7 @@ public final class SmbConfig { private int transactBufferSize; private TransportLayerFactory> transportLayerFactory; private long transactTimeout; + private ExecutorService notifyExecutor; private int soTimeout; @@ -94,7 +96,9 @@ public static Builder builder() { .withDialects(SMB2Dialect.SMB_2_1, SMB2Dialect.SMB_2_0_2) // order is important. The authenticators listed first will be selected .withAuthenticators(getDefaultAuthenticators()) - .withTimeout(DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_UNIT); + .withTimeout(DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_UNIT) + .withNotifyExecutorService(null); + } private static SecurityProvider getDefaultSecurityProvider() { @@ -145,6 +149,7 @@ private SmbConfig(SmbConfig other) { transportLayerFactory = other.transportLayerFactory; soTimeout = other.soTimeout; useMultiProtocolNegotiate = other.useMultiProtocolNegotiate; + notifyExecutor = other.notifyExecutor; } public Random getRandomProvider() { @@ -219,6 +224,10 @@ public SocketFactory getSocketFactory() { return socketFactory; } + public ExecutorService getNotifyExecutorService() { + return notifyExecutor; + } + public static class Builder { private SmbConfig config; @@ -397,5 +406,10 @@ public Builder withMultiProtocolNegotiate(boolean useMultiProtocolNegotiate) { config.useMultiProtocolNegotiate = useMultiProtocolNegotiate; return this; } + + public Builder withNotifyExecutorService(ExecutorService notifyExecutor) { + config.notifyExecutor = notifyExecutor; + return this; + } } } diff --git a/src/main/java/com/hierynomus/smbj/share/DiskShare.java b/src/main/java/com/hierynomus/smbj/share/DiskShare.java index 13bc71e7..a3fb6316 100644 --- a/src/main/java/com/hierynomus/smbj/share/DiskShare.java +++ b/src/main/java/com/hierynomus/smbj/share/DiskShare.java @@ -29,6 +29,7 @@ import com.hierynomus.protocol.commons.EnumWithValue; import com.hierynomus.protocol.commons.buffer.Buffer; import com.hierynomus.protocol.commons.buffer.Endian; +import com.hierynomus.protocol.commons.concurrent.TaskQueue; import com.hierynomus.protocol.transport.TransportException; import com.hierynomus.smb.SMBBuffer; import com.hierynomus.smbj.SMBClient; @@ -81,16 +82,9 @@ public class DiskShare extends Share { private SMBEventBus bus; private NotificationHandler notificationHandler = null; - // TODO: replace this with taskQueue and just having one notify thread pool on Smbj Library - private final ExecutorService notifyExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - Thread t = Executors.defaultThreadFactory().newThread(r); - t.setDaemon(true); - t.setName(smbPath.getShareName() + "-Thread-1"); - return t; - } - }); + private final ExecutorService notifyExecutor; + private final boolean isCreatedNotifyExecutor; + private final TaskQueue taskQueue = new TaskQueue(); public DiskShare(SmbPath smbPath, TreeConnect treeConnect, PathResolver pathResolver, SMBEventBus bus) { super(smbPath, treeConnect); @@ -99,13 +93,31 @@ public DiskShare(SmbPath smbPath, TreeConnect treeConnect, PathResolver pathReso if (bus != null) { bus.subscribe(this); } + ExecutorService executorFromConfig = treeConnect.getConnection().getConfig().getNotifyExecutorService(); + if(executorFromConfig != null) { + notifyExecutor = executorFromConfig; + isCreatedNotifyExecutor = false; + } else { + notifyExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = Executors.defaultThreadFactory().newThread(r); + t.setDaemon(true); + t.setName(DiskShare.super.smbPath.getShareName() + "-Thread-1"); + return t; + } + }); + isCreatedNotifyExecutor = true; + } } @Override public void close() throws IOException { super.close(); - // cleanup for executor - notifyExecutor.shutdown(); + if(isCreatedNotifyExecutor) { + // cleanup for executor + notifyExecutor.shutdown(); + } } public DiskEntry open(String path, Set accessMask, Set attributes, Set shareAccesses, SMB2CreateDisposition createDisposition, Set createOptions) { @@ -582,13 +594,13 @@ private void oplockBreakNotification(final OplockBreakNotification oplockBreakNo if(notificationHandler != null) { // Preventing the improper use of handler (holding the thread). if holding thread, timeout exception will be throw. - notifyExecutor.submit(new Runnable() { + taskQueue.execute(new Runnable() { @Override public void run() { notificationHandler.handle(SMB2_OPLOCK_BREAK_NOTIFICATION, oplockBreakNotification); } - }); + }, notifyExecutor); }else { logger.warn("FileId {}, NotificationHandler not exist to handle Oplock Break.", fileId); throw new IllegalStateException("NotificationHandler not exist to handle Oplock Break."); @@ -611,13 +623,13 @@ private void createRequestNotification(final AsyncCreateRequestNotification asyn try { if(notificationHandler != null) { // Preventing the improper use of handler (holding the thread). if holding thread, timeout exception will be throw. - notifyExecutor.submit(new Runnable() { + taskQueue.execute(new Runnable() { @Override public void run() { notificationHandler.handle(SMB2_CREATE_REQUEST, asyncCreateRequestNotification); } - }); + }, notifyExecutor); }else { logger.debug("NotificationHandler not exist to handle asyncCreateRequestNotification"); } @@ -641,13 +653,13 @@ private void createResponseNotification(final AsyncCreateResponseNotification as try { if(notificationHandler != null) { // Preventing the improper use of handler (holding the thread). if holding thread, timeout exception will be throw. - notifyExecutor.submit(new Runnable() { + taskQueue.execute(new Runnable() { @Override public void run() { notificationHandler.handle(SMB2_CREATE_RESPONSE, asyncCreateResponseNotification); } - }); + }, notifyExecutor); }else { logger.debug("NotificationHandler not exist to handle asyncCreateResponseNotification"); } From bbcf322ea141da9fa55af5f43372138911c16aea Mon Sep 17 00:00:00 2001 From: yin19941005 Date: Mon, 9 Jul 2018 17:13:16 +0800 Subject: [PATCH 03/12] Add factory to create oplock break related message. Add super class for 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. --- .../mssmb2/messages/SMB2MessageConverter.java | 15 ++---- .../mssmb2/messages/SMB2OplockBreak.java | 49 +++++++++++++++++ .../SMB2OplockBreakAcknowledgment.java | 6 +-- ...SMB2OplockBreakAcknowledgmentResponse.java | 29 +---------- .../messages/SMB2OplockBreakFactory.java | 52 +++++++++++++++++++ .../messages/SMB2OplockBreakNotification.java | 28 +--------- .../SMB2OplockBreakServerResponse.java | 33 ++++++++++++ 7 files changed, 140 insertions(+), 72 deletions(-) create mode 100644 src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreak.java create mode 100644 src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakFactory.java create mode 100644 src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakServerResponse.java diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2MessageConverter.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2MessageConverter.java index fdaa8b7e..18c0bdc1 100644 --- a/src/main/java/com/hierynomus/mssmb2/messages/SMB2MessageConverter.java +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2MessageConverter.java @@ -27,6 +27,8 @@ public class SMB2MessageConverter implements PacketFactory { + private SMB2OplockBreakFactory oplockBreakFactory = new SMB2OplockBreakFactory(); + private SMB2Packet read(SMBBuffer buffer) throws Buffer.BufferException { // Check we see a valid header start Check.ensureEquals(buffer.readRawBytes(4), new byte[]{(byte) 0xFE, 'S', 'M', 'B'}, "Could not find SMB2 Packet header"); @@ -69,18 +71,7 @@ private SMB2Packet read(SMBBuffer buffer) throws Buffer.BufferException { case SMB2_SET_INFO: return read(new SMB2SetInfoResponse(), buffer); case SMB2_OPLOCK_BREAK: - // 3.2.5.19 Receiving an SMB2 OPLOCK_BREAK Notification - // If the MessageId field of the SMB2 header of the response is 0xFFFFFFFFFFFFFFFF, - // this MUST be - // processed as an oplock break indication. - buffer.skip(24); - byte[] messageId = buffer.readRawBytes(8); - buffer.rpos(0); - if(Arrays.equals(messageId, (new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}))) { - return read(new SMB2OplockBreakNotification(), buffer); - } else { - return read(new SMB2OplockBreakAcknowledgmentResponse(), buffer); - } + return oplockBreakFactory.read(buffer); case SMB2_LOCK: case SMB2_CANCEL: default: diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreak.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreak.java new file mode 100644 index 00000000..69c2c9a6 --- /dev/null +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreak.java @@ -0,0 +1,49 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.mssmb2.messages; + +import com.hierynomus.mssmb2.SMB2Dialect; +import com.hierynomus.mssmb2.SMB2FileId; +import com.hierynomus.mssmb2.SMB2MessageCommandCode; +import com.hierynomus.mssmb2.SMB2OplockBreakLevel; +import com.hierynomus.mssmb2.SMB2Packet; + +public abstract class SMB2OplockBreak extends SMB2Packet { + + protected SMB2OplockBreakLevel oplockLevel; + protected SMB2FileId fileId; + + protected SMB2OplockBreak() { + super(); + } + + protected SMB2OplockBreak(int structureSize, SMB2Dialect dialect, SMB2MessageCommandCode messageType, long sessionId) { + super(structureSize, dialect, messageType, sessionId); + } + + protected SMB2OplockBreak(int structureSize, SMB2Dialect dialect, SMB2MessageCommandCode messageType, long sessionId, long treeId) { + super(structureSize, dialect, messageType, sessionId, treeId); + } + + public SMB2OplockBreakLevel getOplockLevel() { + return oplockLevel; + } + + public SMB2FileId getFileId() { + return fileId; + } + +} diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgment.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgment.java index 461b2f4c..a4c61780 100644 --- a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgment.java +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgment.java @@ -19,16 +19,12 @@ import com.hierynomus.mssmb2.SMB2FileId; import com.hierynomus.mssmb2.SMB2MessageCommandCode; import com.hierynomus.mssmb2.SMB2OplockBreakLevel; -import com.hierynomus.mssmb2.SMB2Packet; import com.hierynomus.smb.SMBBuffer; /** * [MS-SMB2].pdf 2.2.24 SMB2 OPLOCK_BREAK Acknowledgment */ -public class SMB2OplockBreakAcknowledgment extends SMB2Packet { - - private SMB2OplockBreakLevel oplockLevel; - private SMB2FileId fileId; +public class SMB2OplockBreakAcknowledgment extends SMB2OplockBreak { public SMB2OplockBreakAcknowledgment(SMB2Dialect negotiatedDialect, long sessionId, long treeId, SMB2OplockBreakLevel oplockLevel, SMB2FileId fileId) { super(24, negotiatedDialect, SMB2MessageCommandCode.SMB2_OPLOCK_BREAK, sessionId, treeId); diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgmentResponse.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgmentResponse.java index 8a0b5a6d..b83181ea 100644 --- a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgmentResponse.java +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgmentResponse.java @@ -15,36 +15,9 @@ */ package com.hierynomus.mssmb2.messages; -import com.hierynomus.mssmb2.SMB2FileId; -import com.hierynomus.mssmb2.SMB2OplockBreakLevel; -import com.hierynomus.mssmb2.SMB2Packet; -import com.hierynomus.protocol.commons.EnumWithValue; -import com.hierynomus.protocol.commons.buffer.Buffer; -import com.hierynomus.smb.SMBBuffer; - /*** * [MS-SMB2].pdf 2.2.25 SMB2 OPLOCK_BREAK Response */ -public class SMB2OplockBreakAcknowledgmentResponse extends SMB2Packet { - - private SMB2OplockBreakLevel oplockLevel; - private SMB2FileId fileId; - - @Override - protected void readMessage(SMBBuffer buffer) throws Buffer.BufferException { - buffer.readUInt16(); // StructureSize (2 bytes) - oplockLevel = EnumWithValue.EnumUtils.valueOf(buffer.readByte(), SMB2OplockBreakLevel.class, SMB2OplockBreakLevel.SMB2_OPLOCK_LEVEL_NONE); // OpLockLevel (1 byte) - buffer.readByte(); // Reserved (1 byte) - buffer.skip(4); // Reserved2 (4 bytes) - fileId = SMB2FileId.read(buffer); // FileId (16 bytes) - } - - public SMB2OplockBreakLevel getOplockLevel() { - return oplockLevel; - } - - public SMB2FileId getFileId() { - return fileId; - } +public class SMB2OplockBreakAcknowledgmentResponse extends SMB2OplockBreakServerResponse { } diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakFactory.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakFactory.java new file mode 100644 index 00000000..4843fde0 --- /dev/null +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakFactory.java @@ -0,0 +1,52 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.mssmb2.messages; + +import com.hierynomus.protocol.commons.buffer.Buffer; +import com.hierynomus.smb.SMBBuffer; + +import java.util.Arrays; + +public class SMB2OplockBreakFactory{ + + public SMB2OplockBreak read(SMBBuffer buffer) throws Buffer.BufferException { + // 3.2.5.19 Receiving an SMB2 OPLOCK_BREAK Notification + // If the MessageId field of the SMB2 header of the response is 0xFFFFFFFFFFFFFFFF, + // this MUST be + // processed as an oplock break indication. + 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})); + + // TODO: Use structureSize as well to determine oplock and lease. + // buffer.skip(64); + // final int structureSize = buffer.readUInt16(); + // buffer.rpos(0); + + if(isBreakNotification) { + return read(new SMB2OplockBreakNotification(), buffer); + }else { + return read(new SMB2OplockBreakAcknowledgmentResponse(), buffer); + } + } + + private SMB2OplockBreak read(SMB2OplockBreak packet, SMBBuffer buffer) throws Buffer.BufferException { + packet.read(buffer); + return packet; + } + +} diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakNotification.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakNotification.java index 8d6d19ff..2b89f60d 100644 --- a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakNotification.java +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakNotification.java @@ -15,35 +15,9 @@ */ package com.hierynomus.mssmb2.messages; -import com.hierynomus.mssmb2.SMB2FileId; -import com.hierynomus.mssmb2.SMB2OplockBreakLevel; -import com.hierynomus.mssmb2.SMB2Packet; -import com.hierynomus.protocol.commons.EnumWithValue; -import com.hierynomus.protocol.commons.buffer.Buffer; -import com.hierynomus.smb.SMBBuffer; - /** * [MS-SMB2].pdf 2.2.23 SMB2 OPLOCK_BREAK Notification */ -public class SMB2OplockBreakNotification extends SMB2Packet { - - private SMB2OplockBreakLevel oplockLevel; - private SMB2FileId fileId; - - @Override - protected void readMessage(SMBBuffer buffer) throws Buffer.BufferException { - buffer.readUInt16(); // StructureSize (2 bytes) - oplockLevel = EnumWithValue.EnumUtils.valueOf(buffer.readByte(), SMB2OplockBreakLevel.class, SMB2OplockBreakLevel.SMB2_OPLOCK_LEVEL_NONE); // OpLockLevel (1 byte) - buffer.readByte(); // Reserved (1 byte) - buffer.skip(4); // Reserved2 (4 bytes) - fileId = SMB2FileId.read(buffer); // FileId (16 bytes) - } - - public SMB2OplockBreakLevel getOplockLevel() { - return oplockLevel; - } +public class SMB2OplockBreakNotification extends SMB2OplockBreakServerResponse { - public SMB2FileId getFileId() { - return fileId; - } } diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakServerResponse.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakServerResponse.java new file mode 100644 index 00000000..08fb1f11 --- /dev/null +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakServerResponse.java @@ -0,0 +1,33 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.mssmb2.messages; + +import com.hierynomus.mssmb2.SMB2FileId; +import com.hierynomus.mssmb2.SMB2OplockBreakLevel; +import com.hierynomus.protocol.commons.EnumWithValue; +import com.hierynomus.protocol.commons.buffer.Buffer; +import com.hierynomus.smb.SMBBuffer; + +public class SMB2OplockBreakServerResponse extends SMB2OplockBreak { + @Override + protected void readMessage(SMBBuffer buffer) throws Buffer.BufferException { + buffer.readUInt16(); // StructureSize (2 bytes) + oplockLevel = EnumWithValue.EnumUtils.valueOf(buffer.readByte(), SMB2OplockBreakLevel.class, SMB2OplockBreakLevel.SMB2_OPLOCK_LEVEL_NONE); // OpLockLevel (1 byte) + buffer.readByte(); // Reserved (1 byte) + buffer.skip(4); // Reserved2 (4 bytes) + fileId = SMB2FileId.read(buffer); // FileId (16 bytes) + } +} From 86b602bda451535598cee8e4fafd8098e63c34b1 Mon Sep 17 00:00:00 2001 From: yin19941005 Date: Tue, 10 Jul 2018 11:07:43 +0800 Subject: [PATCH 04/12] Add test case for async create and oplock lock related. --- .../smbj/SMB2FileIntegrationTest.groovy | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) diff --git a/src/it/groovy/com/hierynomus/smbj/SMB2FileIntegrationTest.groovy b/src/it/groovy/com/hierynomus/smbj/SMB2FileIntegrationTest.groovy index 4d599898..06b7f66d 100644 --- a/src/it/groovy/com/hierynomus/smbj/SMB2FileIntegrationTest.groovy +++ b/src/it/groovy/com/hierynomus/smbj/SMB2FileIntegrationTest.groovy @@ -18,22 +18,33 @@ package com.hierynomus.smbj import com.hierynomus.msdtyp.AccessMask import com.hierynomus.mserref.NtStatus import com.hierynomus.mssmb2.SMB2CreateDisposition +import com.hierynomus.mssmb2.SMB2OplockLevel import com.hierynomus.mssmb2.SMB2ShareAccess import com.hierynomus.mssmb2.SMBApiException import com.hierynomus.smb.SMBPacket import com.hierynomus.smbj.auth.AuthenticationContext import com.hierynomus.smbj.connection.Connection +import com.hierynomus.smbj.event.AsyncCreateResponseNotification +import com.hierynomus.smbj.event.AsyncNotification +import com.hierynomus.smbj.event.OplockBreakNotification +import com.hierynomus.smbj.event.handler.MessageIdCallback +import com.hierynomus.smbj.event.handler.NotificationHandler +import com.hierynomus.smbj.event.handler.NotificationMessageType import com.hierynomus.smbj.io.ArrayByteChunkProvider import com.hierynomus.smbj.session.Session +import com.hierynomus.smbj.share.DiskEntry import com.hierynomus.smbj.share.DiskShare import com.hierynomus.smbj.transport.tcp.async.AsyncDirectTcpTransportFactory import spock.lang.Specification import spock.lang.Unroll import java.nio.charset.StandardCharsets +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicBoolean import static com.hierynomus.mssmb2.SMB2CreateDisposition.FILE_CREATE import static com.hierynomus.mssmb2.SMB2CreateDisposition.FILE_OPEN +import static com.hierynomus.mssmb2.SMB2CreateDisposition.FILE_OPEN_IF class SMB2FileIntegrationTest extends Specification { @@ -185,4 +196,201 @@ class SMB2FileIntegrationTest extends Specification { cleanup: share.rm("bigfile") } + + def "should able to async create"() { + given: + def path = "createAsync.txt" + // In actual implementation, the path is not available for createResponse complete. Map is required. + def messageIdPathMap = new ConcurrentHashMap() + // Should call async listener, just calling dummy in test case + def testSucceed = new AtomicBoolean(false) + share.setNotificationHandler( new NotificationHandler() { + + @Override + void handle(NotificationMessageType type, AsyncNotification asyncNotification) { + switch (type) { + case NotificationMessageType.SMB2_CREATE_RESPONSE: + if(asyncNotification instanceof AsyncCreateResponseNotification) { + def asyncCreateResponseNotification = (AsyncCreateResponseNotification)asyncNotification + def createResponseFuture = asyncCreateResponseNotification.future + def createResponse + try { + createResponse = createResponseFuture.get() + } catch (Throwable t) { + throw new IllegalStateException("Unable to get create response", t) + } + def getPath = messageIdPathMap.remove(createResponse.header.messageId) + if(getPath == null) { + System.out.println("Could not find path in map. Should not related to async create, ignored.") + return + } + + if(createResponse.header.status != NtStatus.STATUS_SUCCESS) { + throw new IllegalStateException("Async create failed with status " + createResponse.header.status.value) + } + + def diskEntry = share.getDiskEntry(getPath, new DiskShare.SMB2CreateResponseContext(createResponse, share)) + + if(diskEntry != null) { + // Should call async listener, just calling dummy in test case + testSucceed.compareAndSet(false, true) + } + + } else { + // error handling, dummy in test case + throw new IllegalStateException("Type did't match the actual message") + } + + break + default: + // ignored, not used in this case + break + } + } + }) + + when: + share.openAsync(path, null, null, EnumSet.of(AccessMask.GENERIC_READ, AccessMask.GENERIC_WRITE), null, SMB2ShareAccess.ALL, FILE_CREATE, null, new MessageIdCallback() { + + @Override + void callback(long messageId) { + messageIdPathMap.put(messageId, path) + } + }) + + then: + // 1 second should be enough for the whole process complete in docker + Thread.sleep(1000L) + + expect: + testSucceed.get() == true + + cleanup: + share.rm(path) + messageIdPathMap.clear() + + } + + def "should able to receive oplock break notification and response acknowledgement then receive acknowledgement response"() { + given: + def path = "createAsyncOplock.txt" + // In actual implementation, the path is not available for createResponse complete. Map is required. + def messageIdPathMap = new ConcurrentHashMap() + // Should call async listener, just using hashmap as dummy in test case + def messageIdDiskEntryMap = new ConcurrentHashMap() + def fileIdDiskEntryMap = new ConcurrentHashMap() + def succeedBreakToLevel2 = new AtomicBoolean(false) + def oplockBreakAcknowledgmentResponseSucceed = new AtomicBoolean(false) + share.setNotificationHandler( new NotificationHandler() { + + @Override + void handle(NotificationMessageType type, AsyncNotification asyncNotification) { + switch (type) { + case NotificationMessageType.SMB2_CREATE_RESPONSE: + if(asyncNotification instanceof AsyncCreateResponseNotification) { + def asyncCreateResponseNotification = (AsyncCreateResponseNotification)asyncNotification + def createResponseFuture = asyncCreateResponseNotification.future + def createResponse + try { + createResponse = createResponseFuture.get() + } catch (Throwable t) { + throw new IllegalStateException("Unable to get create response", t) + } + def getPath = messageIdPathMap.remove(createResponse.header.messageId) + if(getPath == null) { + System.out.println("Could not find path in map. Should not related to async create, ignored.") + return + } + + if(createResponse.header.status != NtStatus.STATUS_SUCCESS) { + throw new IllegalStateException("Async create failed with status " + createResponse.header.status.value) + } + + def diskEntry = share.getDiskEntry(getPath, new DiskShare.SMB2CreateResponseContext(createResponse, share)) + + if(diskEntry != null) { + // Should call async listener, just calling dummy in test case + messageIdDiskEntryMap.put(createResponse.header.messageId, diskEntry) + fileIdDiskEntryMap.put(diskEntry.fileId.toHexString(), diskEntry) + } + + } else { + // error handling, dummy in test case + throw new IllegalStateException("Type did't match the actual message") + } + + break + case NotificationMessageType.SMB2_OPLOCK_BREAK_NOTIFICATION: + if(asyncNotification instanceof OplockBreakNotification) { + def oplockBreakNotification = (OplockBreakNotification)asyncNotification + def oplockBreakLevel = oplockBreakNotification.oplockLevel + def getDiskEntry = fileIdDiskEntryMap.get(oplockBreakNotification.fileId.toHexString()) + if(getDiskEntry == null) { + throw new IllegalStateException("Unable to get corresponding diskEntry!") + } + // Assume we already notify client and had succeed handled client cache to break + if(oplockBreakLevel) { + // In this test case, this code should only run exactly once. + succeedBreakToLevel2.compareAndSet(false, true) + } + // Should return to client for handling the client cache, dummy in test case + def oplockBreakAcknowledgmentResponse = getDiskEntry.acknowledgeOplockBreak(oplockBreakLevel) + if(oplockBreakAcknowledgmentResponse.header.status == NtStatus.STATUS_SUCCESS) { + // In this test case, this code should only run exactly once. + oplockBreakAcknowledgmentResponseSucceed.compareAndSet(false, true) + } + }else { + // error handling, dummy in test case + throw new IllegalStateException("Type did't match the actual message") + } + break + default: + // ignored, not used in this case + break + } + } + }) + + when: + def firstCreateMessageId = 0L + share.openAsync(path, SMB2OplockLevel.SMB2_OPLOCK_LEVEL_EXCLUSIVE, null, EnumSet.of(AccessMask.GENERIC_READ, AccessMask.GENERIC_WRITE), null, SMB2ShareAccess.ALL, FILE_OPEN_IF, null, new MessageIdCallback() { + + @Override + void callback(long messageId) { + messageIdPathMap.put(messageId, path) + firstCreateMessageId = messageId + } + }) + + then: + // 1 second should be enough for the whole process complete in docker + Thread.sleep(1000L) + def firstCreateDiskEntry = messageIdDiskEntryMap.remove(firstCreateMessageId) + // another create to the same file with SMB2_OPLOCK_LEVEL_EXCLUSIVE to trigger oplock break notification in Server. + def secondCreateMessageId = 0L + share.openAsync(path, SMB2OplockLevel.SMB2_OPLOCK_LEVEL_EXCLUSIVE, null, EnumSet.of(AccessMask.GENERIC_READ, AccessMask.GENERIC_WRITE), null, SMB2ShareAccess.ALL, FILE_OPEN_IF, null, new MessageIdCallback() { + + @Override + void callback(long messageId) { + messageIdPathMap.put(messageId, path) + secondCreateMessageId = messageId + } + }) + // 1 second should be enough for the whole process complete in docker + Thread.sleep(1000L) + def secondCreateDiskEntry = messageIdDiskEntryMap.remove(secondCreateMessageId) + + expect: + firstCreateDiskEntry != null + secondCreateDiskEntry != null + succeedBreakToLevel2.get() == true + oplockBreakAcknowledgmentResponseSucceed.get() == true + + cleanup: + share.rm(path) + messageIdPathMap.clear() + messageIdDiskEntryMap.clear() + fileIdDiskEntryMap.clear() + + } } From 66a5beff92905d3642421cd957cb70168d8dc101 Mon Sep 17 00:00:00 2001 From: yin19941005 Date: Tue, 10 Jul 2018 14:18:55 +0800 Subject: [PATCH 05/12] Change NotificationHandler to have single handle method for each Notification 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. --- .../smbj/SMB2FileIntegrationTest.groovy | 176 +++++++----------- .../handler/AbstractNotificationHandler.java | 43 +++++ .../event/handler/NotificationHandler.java | 9 +- .../handler/NotificationMessageType.java | 25 --- .../com/hierynomus/smbj/share/DiskShare.java | 12 +- 5 files changed, 123 insertions(+), 142 deletions(-) create mode 100644 src/main/java/com/hierynomus/smbj/event/handler/AbstractNotificationHandler.java delete mode 100644 src/main/java/com/hierynomus/smbj/event/handler/NotificationMessageType.java diff --git a/src/it/groovy/com/hierynomus/smbj/SMB2FileIntegrationTest.groovy b/src/it/groovy/com/hierynomus/smbj/SMB2FileIntegrationTest.groovy index 06b7f66d..47a47da8 100644 --- a/src/it/groovy/com/hierynomus/smbj/SMB2FileIntegrationTest.groovy +++ b/src/it/groovy/com/hierynomus/smbj/SMB2FileIntegrationTest.groovy @@ -21,15 +21,12 @@ import com.hierynomus.mssmb2.SMB2CreateDisposition import com.hierynomus.mssmb2.SMB2OplockLevel import com.hierynomus.mssmb2.SMB2ShareAccess import com.hierynomus.mssmb2.SMBApiException -import com.hierynomus.smb.SMBPacket import com.hierynomus.smbj.auth.AuthenticationContext import com.hierynomus.smbj.connection.Connection import com.hierynomus.smbj.event.AsyncCreateResponseNotification -import com.hierynomus.smbj.event.AsyncNotification import com.hierynomus.smbj.event.OplockBreakNotification +import com.hierynomus.smbj.event.handler.AbstractNotificationHandler import com.hierynomus.smbj.event.handler.MessageIdCallback -import com.hierynomus.smbj.event.handler.NotificationHandler -import com.hierynomus.smbj.event.handler.NotificationMessageType import com.hierynomus.smbj.io.ArrayByteChunkProvider import com.hierynomus.smbj.session.Session import com.hierynomus.smbj.share.DiskEntry @@ -204,49 +201,36 @@ class SMB2FileIntegrationTest extends Specification { def messageIdPathMap = new ConcurrentHashMap() // Should call async listener, just calling dummy in test case def testSucceed = new AtomicBoolean(false) - share.setNotificationHandler( new NotificationHandler() { + share.setNotificationHandler( new AbstractNotificationHandler() { @Override - void handle(NotificationMessageType type, AsyncNotification asyncNotification) { - switch (type) { - case NotificationMessageType.SMB2_CREATE_RESPONSE: - if(asyncNotification instanceof AsyncCreateResponseNotification) { - def asyncCreateResponseNotification = (AsyncCreateResponseNotification)asyncNotification - def createResponseFuture = asyncCreateResponseNotification.future - def createResponse - try { - createResponse = createResponseFuture.get() - } catch (Throwable t) { - throw new IllegalStateException("Unable to get create response", t) - } - def getPath = messageIdPathMap.remove(createResponse.header.messageId) - if(getPath == null) { - System.out.println("Could not find path in map. Should not related to async create, ignored.") - return - } - - if(createResponse.header.status != NtStatus.STATUS_SUCCESS) { - throw new IllegalStateException("Async create failed with status " + createResponse.header.status.value) - } - - def diskEntry = share.getDiskEntry(getPath, new DiskShare.SMB2CreateResponseContext(createResponse, share)) - - if(diskEntry != null) { - // Should call async listener, just calling dummy in test case - testSucceed.compareAndSet(false, true) - } - - } else { - // error handling, dummy in test case - throw new IllegalStateException("Type did't match the actual message") - } + void handleAsyncCreateResponseNotification( + AsyncCreateResponseNotification asyncCreateResponseNotification) { + def createResponseFuture = asyncCreateResponseNotification.future + def createResponse + try { + createResponse = createResponseFuture.get() + } catch (Throwable t) { + throw new IllegalStateException("Unable to get create response", t) + } + def getPath = messageIdPathMap.remove(createResponse.header.messageId) + if(getPath == null) { + System.out.println("Could not find path in map. Should not related to async create, ignored.") + return + } - break - default: - // ignored, not used in this case - break + if(createResponse.header.status != NtStatus.STATUS_SUCCESS) { + throw new IllegalStateException("Async create failed with status " + createResponse.header.status.value) + } + + def diskEntry = share.getDiskEntry(getPath, new DiskShare.SMB2CreateResponseContext(createResponse, share)) + + if(diskEntry != null) { + // Should call async listener, just calling dummy in test case + testSucceed.compareAndSet(false, true) } } + }) when: @@ -281,72 +265,54 @@ class SMB2FileIntegrationTest extends Specification { def fileIdDiskEntryMap = new ConcurrentHashMap() def succeedBreakToLevel2 = new AtomicBoolean(false) def oplockBreakAcknowledgmentResponseSucceed = new AtomicBoolean(false) - share.setNotificationHandler( new NotificationHandler() { + share.setNotificationHandler( new AbstractNotificationHandler() { @Override - void handle(NotificationMessageType type, AsyncNotification asyncNotification) { - switch (type) { - case NotificationMessageType.SMB2_CREATE_RESPONSE: - if(asyncNotification instanceof AsyncCreateResponseNotification) { - def asyncCreateResponseNotification = (AsyncCreateResponseNotification)asyncNotification - def createResponseFuture = asyncCreateResponseNotification.future - def createResponse - try { - createResponse = createResponseFuture.get() - } catch (Throwable t) { - throw new IllegalStateException("Unable to get create response", t) - } - def getPath = messageIdPathMap.remove(createResponse.header.messageId) - if(getPath == null) { - System.out.println("Could not find path in map. Should not related to async create, ignored.") - return - } - - if(createResponse.header.status != NtStatus.STATUS_SUCCESS) { - throw new IllegalStateException("Async create failed with status " + createResponse.header.status.value) - } - - def diskEntry = share.getDiskEntry(getPath, new DiskShare.SMB2CreateResponseContext(createResponse, share)) - - if(diskEntry != null) { - // Should call async listener, just calling dummy in test case - messageIdDiskEntryMap.put(createResponse.header.messageId, diskEntry) - fileIdDiskEntryMap.put(diskEntry.fileId.toHexString(), diskEntry) - } - - } else { - // error handling, dummy in test case - throw new IllegalStateException("Type did't match the actual message") - } + void handleAsyncCreateResponseNotification( + AsyncCreateResponseNotification asyncCreateResponseNotification) { + def createResponseFuture = asyncCreateResponseNotification.future + def createResponse + try { + createResponse = createResponseFuture.get() + } catch (Throwable t) { + throw new IllegalStateException("Unable to get create response", t) + } + def getPath = messageIdPathMap.remove(createResponse.header.messageId) + if(getPath == null) { + System.out.println("Could not find path in map. Should not related to async create, ignored.") + return + } - break - case NotificationMessageType.SMB2_OPLOCK_BREAK_NOTIFICATION: - if(asyncNotification instanceof OplockBreakNotification) { - def oplockBreakNotification = (OplockBreakNotification)asyncNotification - def oplockBreakLevel = oplockBreakNotification.oplockLevel - def getDiskEntry = fileIdDiskEntryMap.get(oplockBreakNotification.fileId.toHexString()) - if(getDiskEntry == null) { - throw new IllegalStateException("Unable to get corresponding diskEntry!") - } - // Assume we already notify client and had succeed handled client cache to break - if(oplockBreakLevel) { - // In this test case, this code should only run exactly once. - succeedBreakToLevel2.compareAndSet(false, true) - } - // Should return to client for handling the client cache, dummy in test case - def oplockBreakAcknowledgmentResponse = getDiskEntry.acknowledgeOplockBreak(oplockBreakLevel) - if(oplockBreakAcknowledgmentResponse.header.status == NtStatus.STATUS_SUCCESS) { - // In this test case, this code should only run exactly once. - oplockBreakAcknowledgmentResponseSucceed.compareAndSet(false, true) - } - }else { - // error handling, dummy in test case - throw new IllegalStateException("Type did't match the actual message") - } - break - default: - // ignored, not used in this case - break + if(createResponse.header.status != NtStatus.STATUS_SUCCESS) { + throw new IllegalStateException("Async create failed with status " + createResponse.header.status.value) + } + + def diskEntry = share.getDiskEntry(getPath, new DiskShare.SMB2CreateResponseContext(createResponse, share)) + + if(diskEntry != null) { + // Should call async listener, just calling dummy in test case + messageIdDiskEntryMap.put(createResponse.header.messageId, diskEntry) + fileIdDiskEntryMap.put(diskEntry.fileId.toHexString(), diskEntry) + } + } + + @Override + void handleOplockBreakNotification(OplockBreakNotification oplockBreakNotification) { + def oplockBreakLevel = oplockBreakNotification.oplockLevel + def getDiskEntry = fileIdDiskEntryMap.get(oplockBreakNotification.fileId.toHexString()) + if(getDiskEntry == null) { + throw new IllegalStateException("Unable to get corresponding diskEntry!") + } + // Assume we already notify client and had succeed handled client cache to break + if(oplockBreakLevel) { + // In this test case, this code should only run exactly once. + succeedBreakToLevel2.compareAndSet(false, true) + } + // Should return to client for handling the client cache, dummy in test case + def oplockBreakAcknowledgmentResponse = getDiskEntry.acknowledgeOplockBreak(oplockBreakLevel) + if(oplockBreakAcknowledgmentResponse.header.status == NtStatus.STATUS_SUCCESS) { + // In this test case, this code should only run exactly once. + oplockBreakAcknowledgmentResponseSucceed.compareAndSet(false, true) } } }) diff --git a/src/main/java/com/hierynomus/smbj/event/handler/AbstractNotificationHandler.java b/src/main/java/com/hierynomus/smbj/event/handler/AbstractNotificationHandler.java new file mode 100644 index 00000000..7172f11b --- /dev/null +++ b/src/main/java/com/hierynomus/smbj/event/handler/AbstractNotificationHandler.java @@ -0,0 +1,43 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.smbj.event.handler; + +import com.hierynomus.smbj.event.AsyncCreateRequestNotification; +import com.hierynomus.smbj.event.AsyncCreateResponseNotification; +import com.hierynomus.smbj.event.OplockBreakNotification; + +/*** + * Abstract class for user only interested in some notification to override + */ +public abstract class AbstractNotificationHandler implements NotificationHandler { + + @Override + public void handleAsyncCreateRequestNotification( + AsyncCreateRequestNotification asyncCreateRequestNotification) { + + } + + @Override + public void handleAsyncCreateResponseNotification( + AsyncCreateResponseNotification asyncCreateResponseNotification) { + + } + + @Override + public void handleOplockBreakNotification(OplockBreakNotification oplockBreakNotification) { + + } +} diff --git a/src/main/java/com/hierynomus/smbj/event/handler/NotificationHandler.java b/src/main/java/com/hierynomus/smbj/event/handler/NotificationHandler.java index 02de56b0..855b026a 100644 --- a/src/main/java/com/hierynomus/smbj/event/handler/NotificationHandler.java +++ b/src/main/java/com/hierynomus/smbj/event/handler/NotificationHandler.java @@ -15,9 +15,12 @@ */ package com.hierynomus.smbj.event.handler; -import com.hierynomus.smbj.event.AsyncNotification; +import com.hierynomus.smbj.event.AsyncCreateRequestNotification; +import com.hierynomus.smbj.event.AsyncCreateResponseNotification; +import com.hierynomus.smbj.event.OplockBreakNotification; public interface NotificationHandler { - void handle(NotificationMessageType type, - AsyncNotification asyncNotification); + void handleAsyncCreateRequestNotification(AsyncCreateRequestNotification asyncCreateRequestNotification); + void handleAsyncCreateResponseNotification(AsyncCreateResponseNotification asyncCreateResponseNotification); + void handleOplockBreakNotification(OplockBreakNotification oplockBreakNotification); } diff --git a/src/main/java/com/hierynomus/smbj/event/handler/NotificationMessageType.java b/src/main/java/com/hierynomus/smbj/event/handler/NotificationMessageType.java deleted file mode 100644 index 8d8423ac..00000000 --- a/src/main/java/com/hierynomus/smbj/event/handler/NotificationMessageType.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C)2016 - SMBJ Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.hierynomus.smbj.event.handler; - -/*** - * The enum class for determine which type of message is sending to NotificationHandler - */ -public enum NotificationMessageType { - SMB2_CREATE_REQUEST, - SMB2_CREATE_RESPONSE, - SMB2_OPLOCK_BREAK_NOTIFICATION -} diff --git a/src/main/java/com/hierynomus/smbj/share/DiskShare.java b/src/main/java/com/hierynomus/smbj/share/DiskShare.java index a3fb6316..e0d24502 100644 --- a/src/main/java/com/hierynomus/smbj/share/DiskShare.java +++ b/src/main/java/com/hierynomus/smbj/share/DiskShare.java @@ -70,9 +70,6 @@ import static com.hierynomus.mssmb2.SMB2CreateOptions.FILE_NON_DIRECTORY_FILE; import static com.hierynomus.mssmb2.SMB2ShareAccess.*; import static com.hierynomus.mssmb2.messages.SMB2QueryInfoRequest.SMB2QueryInfoType.SMB2_0_INFO_SECURITY; -import static com.hierynomus.smbj.event.handler.NotificationMessageType.SMB2_CREATE_REQUEST; -import static com.hierynomus.smbj.event.handler.NotificationMessageType.SMB2_CREATE_RESPONSE; -import static com.hierynomus.smbj.event.handler.NotificationMessageType.SMB2_OPLOCK_BREAK_NOTIFICATION; import static java.util.EnumSet.of; import static java.util.EnumSet.noneOf; @@ -597,8 +594,7 @@ private void oplockBreakNotification(final OplockBreakNotification oplockBreakNo taskQueue.execute(new Runnable() { @Override public void run() { - notificationHandler.handle(SMB2_OPLOCK_BREAK_NOTIFICATION, - oplockBreakNotification); + notificationHandler.handleOplockBreakNotification(oplockBreakNotification); } }, notifyExecutor); }else { @@ -626,8 +622,7 @@ private void createRequestNotification(final AsyncCreateRequestNotification asyn taskQueue.execute(new Runnable() { @Override public void run() { - notificationHandler.handle(SMB2_CREATE_REQUEST, - asyncCreateRequestNotification); + notificationHandler.handleAsyncCreateRequestNotification(asyncCreateRequestNotification); } }, notifyExecutor); }else { @@ -656,8 +651,7 @@ private void createResponseNotification(final AsyncCreateResponseNotification as taskQueue.execute(new Runnable() { @Override public void run() { - notificationHandler.handle(SMB2_CREATE_RESPONSE, - asyncCreateResponseNotification); + notificationHandler.handleAsyncCreateResponseNotification(asyncCreateResponseNotification); } }, notifyExecutor); }else { From 9df5b0f34ef7143aac8f9958d5d727bf2337040e Mon Sep 17 00:00:00 2001 From: yin19941005 Date: Tue, 10 Jul 2018 15:49:57 +0800 Subject: [PATCH 06/12] Reverse the order of SMBJ Apache license and Eclipse Vert.x Apache license in taskQueue class to fit CI requirement. --- .../commons/concurrent/TaskQueue.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/hierynomus/protocol/commons/concurrent/TaskQueue.java b/src/main/java/com/hierynomus/protocol/commons/concurrent/TaskQueue.java index 60c55565..49cad962 100644 --- a/src/main/java/com/hierynomus/protocol/commons/concurrent/TaskQueue.java +++ b/src/main/java/com/hierynomus/protocol/commons/concurrent/TaskQueue.java @@ -1,13 +1,3 @@ -/* - * Copyright (c) 2011-2017 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ /* * Copyright (C)2016 - SMBJ Contributors * @@ -23,6 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +/* + * Copyright (c) 2011-2017 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ // Modified: Changed the package package com.hierynomus.protocol.commons.concurrent; From b8c878d6bba67ef3e12bef53db65b231b49e40be Mon Sep 17 00:00:00 2001 From: yin19941005 Date: Fri, 13 Jul 2018 18:28:20 +0800 Subject: [PATCH 07/12] Fix didn't check should handle or not when receive a async notification. 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. --- .../com/hierynomus/mssmb2/SMB2FileId.java | 5 +- .../messages/SMB2OplockBreakFactory.java | 24 +++--- .../commons/concurrent/FutureWrapper.java | 58 ------------- .../smbj/connection/Connection.java | 26 ++++-- .../smbj/event/AbstractAsyncNotification.java | 38 +++++++++ .../event/AsyncCreateRequestNotification.java | 13 +-- .../AsyncCreateResponseNotification.java | 16 ++-- .../smbj/event/AsyncNotification.java | 7 +- .../smbj/event/OplockBreakNotification.java | 4 +- .../event/handler/NotificationHandler.java | 1 + .../com/hierynomus/smbj/share/DiskShare.java | 83 ++++++++++++++----- .../java/com/hierynomus/smbj/share/Share.java | 4 +- 12 files changed, 157 insertions(+), 122 deletions(-) delete mode 100644 src/main/java/com/hierynomus/protocol/commons/concurrent/FutureWrapper.java create mode 100644 src/main/java/com/hierynomus/smbj/event/AbstractAsyncNotification.java diff --git a/src/main/java/com/hierynomus/mssmb2/SMB2FileId.java b/src/main/java/com/hierynomus/mssmb2/SMB2FileId.java index 872d86b6..ed356c27 100644 --- a/src/main/java/com/hierynomus/mssmb2/SMB2FileId.java +++ b/src/main/java/com/hierynomus/mssmb2/SMB2FileId.java @@ -16,7 +16,6 @@ package com.hierynomus.mssmb2; import com.hierynomus.protocol.commons.ByteArrayUtils; -import com.hierynomus.protocol.commons.Objects; import com.hierynomus.protocol.commons.buffer.Buffer; import com.hierynomus.smb.SMBBuffer; @@ -66,8 +65,8 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SMB2FileId smb2FileId = (SMB2FileId) o; - return Objects.equals(persistentHandle, smb2FileId.persistentHandle) && - Objects.equals(volatileHandle, smb2FileId.volatileHandle); + return Arrays.equals(persistentHandle, smb2FileId.persistentHandle) && + Arrays.equals(volatileHandle, smb2FileId.volatileHandle); } @Override diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakFactory.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakFactory.java index 4843fde0..4279196d 100644 --- a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakFactory.java +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakFactory.java @@ -18,33 +18,35 @@ import com.hierynomus.protocol.commons.buffer.Buffer; import com.hierynomus.smb.SMBBuffer; -import java.util.Arrays; +public class SMB2OplockBreakFactory { -public class SMB2OplockBreakFactory{ + // 3.2.5.19 Receiving an SMB2 OPLOCK_BREAK Notification + // If the MessageId field of the SMB2 header of the response is 0xFFFFFFFFFFFFFFFF, + // this MUST be processed as an oplock break indication. + // 0xFFFFFFFFFFFFFFFF == -1 + private static final long breakMessageId = -1; public SMB2OplockBreak read(SMBBuffer buffer) throws Buffer.BufferException { - // 3.2.5.19 Receiving an SMB2 OPLOCK_BREAK Notification - // If the MessageId field of the SMB2 header of the response is 0xFFFFFFFFFFFFFFFF, - // this MUST be - // processed as an oplock break indication. + buffer.skip(24); - byte[] messageId = buffer.readRawBytes(8); + long messageId = buffer.readLong(); 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})); + final boolean isBreakNotification = messageId == breakMessageId; // TODO: Use structureSize as well to determine oplock and lease. // buffer.skip(64); // final int structureSize = buffer.readUInt16(); // buffer.rpos(0); - if(isBreakNotification) { + if (isBreakNotification) { return read(new SMB2OplockBreakNotification(), buffer); - }else { + } else { return read(new SMB2OplockBreakAcknowledgmentResponse(), buffer); } } - private SMB2OplockBreak read(SMB2OplockBreak packet, SMBBuffer buffer) throws Buffer.BufferException { + private SMB2OplockBreak read(SMB2OplockBreak packet, SMBBuffer buffer) + throws Buffer.BufferException { packet.read(buffer); return packet; } diff --git a/src/main/java/com/hierynomus/protocol/commons/concurrent/FutureWrapper.java b/src/main/java/com/hierynomus/protocol/commons/concurrent/FutureWrapper.java deleted file mode 100644 index 1a98a279..00000000 --- a/src/main/java/com/hierynomus/protocol/commons/concurrent/FutureWrapper.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C)2016 - SMBJ Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.hierynomus.protocol.commons.concurrent; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -public class FutureWrapper implements Future { - - private final Future mDelegate; - - public FutureWrapper(Future delegate) { - mDelegate = delegate; - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - return mDelegate.cancel(mayInterruptIfRunning); - } - - @Override - public boolean isCancelled() { - return mDelegate.isCancelled(); - } - - @Override - public boolean isDone() { - return mDelegate.isDone(); - } - - @Override - public T get() throws InterruptedException, ExecutionException { - //noinspection unchecked - return (T)mDelegate.get(); - } - - @Override - public T get(long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - //noinspection unchecked - return (T)mDelegate.get(timeout, unit); - } -} diff --git a/src/main/java/com/hierynomus/smbj/connection/Connection.java b/src/main/java/com/hierynomus/smbj/connection/Connection.java index 6421c0d5..5e902adb 100644 --- a/src/main/java/com/hierynomus/smbj/connection/Connection.java +++ b/src/main/java/com/hierynomus/smbj/connection/Connection.java @@ -25,7 +25,6 @@ import com.hierynomus.protocol.commons.Factory; import com.hierynomus.protocol.commons.buffer.Buffer; import com.hierynomus.protocol.commons.concurrent.CancellableFuture; -import com.hierynomus.protocol.commons.concurrent.FutureWrapper; import com.hierynomus.protocol.commons.concurrent.Futures; import com.hierynomus.protocol.transport.*; import com.hierynomus.smb.SMBPacket; @@ -292,10 +291,12 @@ public Future send(SMB2Packet packet, MessageIdCallbac messageIdCallback.callback(messageId); } - if(packet instanceof SMB2CreateRequest) { - SMB2CreateRequest createRequest = (SMB2CreateRequest)packet; - SmbPath path = createRequest.getPath(); - bus.publish(new AsyncCreateRequestNotification(messageId, path)); + if(packet.getHeader().getMessage() == SMB2MessageCommandCode.SMB2_CREATE) { + bus.publish(new AsyncCreateRequestNotification( + packet.getHeader().getSessionId(), + packet.getHeader().getTreeId(), + messageId) + ); } Request request = new Request(packet.getHeader().getMessageId(), UUID.randomUUID()); @@ -409,7 +410,10 @@ public void handle(SMBPacket uncheckedPacket) throws TransportException { if(packet instanceof SMB2OplockBreakNotification) { SMB2OplockBreakNotification oplockBreakNotification = (SMB2OplockBreakNotification)packet; logger.debug("Received SMB2OplockBreakNotification Packet for FileId {} with {}", oplockBreakNotification.getFileId(), oplockBreakNotification.getOplockLevel()); - bus.publish(new OplockBreakNotification(oplockBreakNotification.getOplockLevel(), oplockBreakNotification.getFileId())); + bus.publish(new OplockBreakNotification( + oplockBreakNotification.getOplockLevel(), + oplockBreakNotification.getFileId() + )); return; } @@ -456,8 +460,14 @@ public void handle(SMBPacket uncheckedPacket) throws TransportException { // Handing case for Oplock/Lease related issue if(packet instanceof SMB2CreateResponse) { SMB2CreateResponse smb2CreateResponse = (SMB2CreateResponse)packet; - Future future = new FutureWrapper<>(outstandingRequests.getRequestByMessageId(messageId).getPromise().future()); - bus.publish(new AsyncCreateResponseNotification(messageId, smb2CreateResponse.getFileId(), future)); + bus.publish(new AsyncCreateResponseNotification( + smb2CreateResponse.getHeader().getSessionId(), + smb2CreateResponse.getHeader().getTreeId(), + messageId, + smb2CreateResponse.getFileId(), + smb2CreateResponse) + ); + } // [MS-SMB2].pdf 3.2.5.1.8 Processing the Response diff --git a/src/main/java/com/hierynomus/smbj/event/AbstractAsyncNotification.java b/src/main/java/com/hierynomus/smbj/event/AbstractAsyncNotification.java new file mode 100644 index 00000000..135c9ba3 --- /dev/null +++ b/src/main/java/com/hierynomus/smbj/event/AbstractAsyncNotification.java @@ -0,0 +1,38 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.smbj.event; + +public abstract class AbstractAsyncNotification implements AsyncNotification { + + private long sessionId; + private long treeId; + + public AbstractAsyncNotification(long sessionId, long treeId) { + this.sessionId = sessionId; + this.treeId = treeId; + } + + @Override + public long getSessionId() { + return sessionId; + } + + @Override + public long getTreeId() { + return treeId; + } +} + diff --git a/src/main/java/com/hierynomus/smbj/event/AsyncCreateRequestNotification.java b/src/main/java/com/hierynomus/smbj/event/AsyncCreateRequestNotification.java index 338f5262..fd793213 100644 --- a/src/main/java/com/hierynomus/smbj/event/AsyncCreateRequestNotification.java +++ b/src/main/java/com/hierynomus/smbj/event/AsyncCreateRequestNotification.java @@ -18,23 +18,18 @@ import com.hierynomus.smbj.common.SmbPath; /*** - * Event for notifying the SmbPath to DiskShare Notification Handler + * Event for notifying the messageId to DiskShare Notification Handler */ -public class AsyncCreateRequestNotification implements SMBEvent, AsyncNotification { +public class AsyncCreateRequestNotification extends AbstractAsyncNotification implements SMBEvent { private long messageId; - private SmbPath path; - public AsyncCreateRequestNotification(long messageId, SmbPath path) { + public AsyncCreateRequestNotification(long sessionId, long treeId, long messageId) { + super(sessionId, treeId); this.messageId = messageId; - this.path = path; } public long getMessageId() { return messageId; } - - public SmbPath getPath() { - return path; - } } diff --git a/src/main/java/com/hierynomus/smbj/event/AsyncCreateResponseNotification.java b/src/main/java/com/hierynomus/smbj/event/AsyncCreateResponseNotification.java index 97acfa6a..f9751084 100644 --- a/src/main/java/com/hierynomus/smbj/event/AsyncCreateResponseNotification.java +++ b/src/main/java/com/hierynomus/smbj/event/AsyncCreateResponseNotification.java @@ -18,21 +18,21 @@ import com.hierynomus.mssmb2.SMB2FileId; import com.hierynomus.mssmb2.messages.SMB2CreateResponse; -import java.util.concurrent.Future; - /*** * Event for notifying the fileId and CreateResponseFuture to corresponding messageId on AysncCreate */ -public class AsyncCreateResponseNotification implements SMBEvent, AsyncNotification { +public class AsyncCreateResponseNotification extends AbstractAsyncNotification implements SMBEvent { private long messageId; private SMB2FileId fileId; - private Future future; + private SMB2CreateResponse createResponse; - public AsyncCreateResponseNotification(long messageId, SMB2FileId fileId, Future future) { + public AsyncCreateResponseNotification(long sessionId, long treeId, long messageId, + SMB2FileId fileId, SMB2CreateResponse createResponse) { + super(sessionId, treeId); this.messageId = messageId; this.fileId = fileId; - this.future = future; + this.createResponse = createResponse; } public long getMessageId() { @@ -43,7 +43,7 @@ public SMB2FileId getFileId() { return fileId; } - public Future getFuture() { - return future; + public SMB2CreateResponse getCreateResponse() { + return createResponse; } } diff --git a/src/main/java/com/hierynomus/smbj/event/AsyncNotification.java b/src/main/java/com/hierynomus/smbj/event/AsyncNotification.java index 45b6984a..21f0e7fa 100644 --- a/src/main/java/com/hierynomus/smbj/event/AsyncNotification.java +++ b/src/main/java/com/hierynomus/smbj/event/AsyncNotification.java @@ -16,7 +16,12 @@ package com.hierynomus.smbj.event; /** - * Base class for asynchronous notification events that need to be handled by notification handlers (observers) + * Base class for asynchronous notification events that need to be handled by notification + * handlers (observers) */ public interface AsyncNotification { + + long getSessionId(); + + long getTreeId(); } diff --git a/src/main/java/com/hierynomus/smbj/event/OplockBreakNotification.java b/src/main/java/com/hierynomus/smbj/event/OplockBreakNotification.java index b4009238..8e33efc7 100644 --- a/src/main/java/com/hierynomus/smbj/event/OplockBreakNotification.java +++ b/src/main/java/com/hierynomus/smbj/event/OplockBreakNotification.java @@ -21,12 +21,14 @@ /*** * Event for notifying the oplock break notification for corresponding fileId */ -public class OplockBreakNotification implements SMBEvent, AsyncNotification { +public class OplockBreakNotification extends AbstractAsyncNotification implements SMBEvent { private SMB2OplockBreakLevel oplockLevel; private SMB2FileId fileId; public OplockBreakNotification(SMB2OplockBreakLevel oplockLevel, SMB2FileId fileId) { + // will always getting 0 for sessionId and treeId for oplock break notification. + super(0L, 0L); this.oplockLevel = oplockLevel; this.fileId = fileId; } diff --git a/src/main/java/com/hierynomus/smbj/event/handler/NotificationHandler.java b/src/main/java/com/hierynomus/smbj/event/handler/NotificationHandler.java index 855b026a..7490576d 100644 --- a/src/main/java/com/hierynomus/smbj/event/handler/NotificationHandler.java +++ b/src/main/java/com/hierynomus/smbj/event/handler/NotificationHandler.java @@ -20,6 +20,7 @@ import com.hierynomus.smbj.event.OplockBreakNotification; public interface NotificationHandler { + // TODO: add user reference property to allow user to check same NotificationHandler or not. void handleAsyncCreateRequestNotification(AsyncCreateRequestNotification asyncCreateRequestNotification); void handleAsyncCreateResponseNotification(AsyncCreateResponseNotification asyncCreateResponseNotification); void handleOplockBreakNotification(OplockBreakNotification oplockBreakNotification); diff --git a/src/main/java/com/hierynomus/smbj/share/DiskShare.java b/src/main/java/com/hierynomus/smbj/share/DiskShare.java index e0d24502..435779ca 100644 --- a/src/main/java/com/hierynomus/smbj/share/DiskShare.java +++ b/src/main/java/com/hierynomus/smbj/share/DiskShare.java @@ -35,7 +35,6 @@ import com.hierynomus.smbj.SMBClient; import com.hierynomus.smbj.common.SMBRuntimeException; import com.hierynomus.smbj.common.SmbPath; -import com.hierynomus.smbj.connection.Connection; import com.hierynomus.smbj.event.AsyncCreateRequestNotification; import com.hierynomus.smbj.event.AsyncCreateResponseNotification; import com.hierynomus.smbj.event.OplockBreakNotification; @@ -52,9 +51,11 @@ import org.slf4j.LoggerFactory; import java.io.IOException; +import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -82,6 +83,10 @@ public class DiskShare extends Share { private final ExecutorService notifyExecutor; private final boolean isCreatedNotifyExecutor; private final TaskQueue taskQueue = new TaskQueue(); + // TODO: ensure the event is only used by one Connect instance + private final Set openedOplockFileId = Collections.newSetFromMap(new ConcurrentHashMap()); + // TODO: Implement a internal centralize messageId callback to record all implemented notificationHandler MessageId + private final Set createRequestMessageId = Collections.newSetFromMap(new ConcurrentHashMap()); public DiskShare(SmbPath smbPath, TreeConnect treeConnect, PathResolver pathResolver, SMBEventBus bus) { super(smbPath, treeConnect); @@ -115,6 +120,9 @@ public void close() throws IOException { // cleanup for executor notifyExecutor.shutdown(); } + // cleanup for set + openedOplockFileId.clear(); + createRequestMessageId.clear(); } public DiskEntry open(String path, Set accessMask, Set attributes, Set shareAccesses, SMB2CreateDisposition createDisposition, Set createOptions) { @@ -218,11 +226,17 @@ private Session buildNewSession(SMB2CreateResponse resp, SmbPath target) { public DiskEntry getDiskEntry(String path, SMB2CreateResponseContext responseContext) { SMB2CreateResponse response = responseContext.resp; + DiskEntry diskEntry; if (response.getFileAttributes().contains(FILE_ATTRIBUTE_DIRECTORY)) { - return new Directory(response.getFileId(), responseContext.share, path); + diskEntry = new Directory(response.getFileId(), responseContext.share, path); } else { - return new File(response.getFileId(), responseContext.share, path); + diskEntry = new File(response.getFileId(), responseContext.share, path); } + // if oplock level is not none, put it to set. + if (response.getOplockLevel() != SMB2OplockLevel.SMB2_OPLOCK_LEVEL_NONE) { + openedOplockFileId.add(diskEntry.fileId); + } + return diskEntry; } /** @@ -285,6 +299,13 @@ public File openFile(String path, SMB2OplockLevel oplockLevel, SMB2Impersonation ); } + @Override + void closeFileId(SMB2FileId fileId) throws SMBApiException { + super.closeFileId(fileId); + // remove the the fileId from set when success, i.e. no Exception throws + openedOplockFileId.remove(fileId); + } + /** * File in the given path exists or not */ @@ -591,12 +612,15 @@ private void oplockBreakNotification(final OplockBreakNotification oplockBreakNo if(notificationHandler != null) { // Preventing the improper use of handler (holding the thread). if holding thread, timeout exception will be throw. - taskQueue.execute(new Runnable() { - @Override - public void run() { - notificationHandler.handleOplockBreakNotification(oplockBreakNotification); - } - }, notifyExecutor); + if (openedOplockFileId.contains(fileId)) { + // submit to taskQueue only when this DiskShare opened a handle with this fileId. Otherwise, ignore it. + taskQueue.execute(new Runnable() { + @Override + public void run() { + notificationHandler.handleOplockBreakNotification(oplockBreakNotification); + } + }, notifyExecutor); + } }else { logger.warn("FileId {}, NotificationHandler not exist to handle Oplock Break.", fileId); throw new IllegalStateException("NotificationHandler not exist to handle Oplock Break."); @@ -619,12 +643,22 @@ private void createRequestNotification(final AsyncCreateRequestNotification asyn try { if(notificationHandler != null) { // Preventing the improper use of handler (holding the thread). if holding thread, timeout exception will be throw. - taskQueue.execute(new Runnable() { - @Override - public void run() { - notificationHandler.handleAsyncCreateRequestNotification(asyncCreateRequestNotification); - } - }, notifyExecutor); + // Checking treeId can always map a DiskShare for AsyncCreateRequestNotification, because this happens before sending message. + if(asyncCreateRequestNotification.getSessionId() == this.sessionId + && asyncCreateRequestNotification.getTreeId() == this.treeId) { + // add the messageId to Set if createRequest is sending by this DiskShare + createRequestMessageId.add(asyncCreateRequestNotification.getMessageId()); + + // submit to taskQueue only when sessionId and treeId match. Otherwise, ignore it. + taskQueue.execute(new Runnable() { + @Override + public void run() { + notificationHandler.handleAsyncCreateRequestNotification(asyncCreateRequestNotification); + } + }, notifyExecutor); + }else { + logger.debug("asyncCreateRequestNotification ignored. this.treeId = " + this.treeId + ", notification.getTreeId() = " + asyncCreateRequestNotification.getTreeId()); + } }else { logger.debug("NotificationHandler not exist to handle asyncCreateRequestNotification"); } @@ -648,12 +682,19 @@ private void createResponseNotification(final AsyncCreateResponseNotification as try { if(notificationHandler != null) { // Preventing the improper use of handler (holding the thread). if holding thread, timeout exception will be throw. - taskQueue.execute(new Runnable() { - @Override - public void run() { - notificationHandler.handleAsyncCreateResponseNotification(asyncCreateResponseNotification); - } - }, notifyExecutor); + boolean shouldHandle = createRequestMessageId.remove(asyncCreateResponseNotification.getMessageId()); + if(shouldHandle) { + // submit to taskQueue only if createRequest is sent out by this DiskShare. Otherwise, ignore it. + taskQueue.execute(new Runnable() { + @Override + public void run() { + notificationHandler.handleAsyncCreateResponseNotification( + asyncCreateResponseNotification); + } + }, notifyExecutor); + }else { + logger.debug("asyncCreateResponseNotification ignored. MessageId = " + asyncCreateResponseNotification.getMessageId() + ", is not handled by this.treeId = " + this.treeId); + } }else { logger.debug("NotificationHandler not exist to handle asyncCreateResponseNotification"); } diff --git a/src/main/java/com/hierynomus/smbj/share/Share.java b/src/main/java/com/hierynomus/smbj/share/Share.java index a2c33fc4..fa37ca8c 100644 --- a/src/main/java/com/hierynomus/smbj/share/Share.java +++ b/src/main/java/com/hierynomus/smbj/share/Share.java @@ -56,7 +56,7 @@ public class Share implements AutoCloseable { protected final SmbPath smbPath; protected final TreeConnect treeConnect; - private final long treeId; + protected final long treeId; protected Session session; private final SMB2Dialect dialect; private final int readBufferSize; @@ -65,7 +65,7 @@ public class Share implements AutoCloseable { private final long writeTimeout; private final int transactBufferSize; private final long transactTimeout; - private final long sessionId; + protected final long sessionId; private final AtomicBoolean disconnected = new AtomicBoolean(false); Share(SmbPath smbPath, TreeConnect treeConnect) { From a48ad30bb2bf171baf5f661317b38fc2a18f603a Mon Sep 17 00:00:00 2001 From: yin19941005 Date: Tue, 17 Jul 2018 16:13:18 +0800 Subject: [PATCH 08/12] Fix publishing connection internal event through global EventBus. Enhance 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. --- .../smbj/SMB2FileIntegrationTest.groovy | 23 +--- .../mssmb2/messages/SMB2OplockBreak.java | 8 +- .../SMB2OplockBreakAcknowledgment.java | 2 +- .../commons/concurrent/TaskQueue.java | 125 ++++++------------ .../java/com/hierynomus/smbj/SMBClient.java | 10 +- .../smbj/connection/Connection.java | 44 +++--- ... => AbstractAsyncRequestNotification.java} | 5 +- .../AbstractAsyncResponseNotification.java | 30 +++++ .../event/AsyncCreateRequestNotification.java | 3 +- .../AsyncCreateResponseNotification.java | 15 +-- .../smbj/event/AsyncNotification.java | 3 - .../AsyncRequestMessageIdNotification.java | 41 ++++++ .../smbj/event/AsyncRequestNotification.java | 23 ++++ .../smbj/event/AsyncResponseNotification.java | 21 +++ .../smbj/event/OplockBreakNotification.java | 4 +- .../com/hierynomus/smbj/session/Session.java | 16 +-- .../com/hierynomus/smbj/share/DiskShare.java | 116 +++++++++------- .../hierynomus/smbj/share/TreeConnect.java | 8 +- 18 files changed, 292 insertions(+), 205 deletions(-) rename src/main/java/com/hierynomus/smbj/event/{AbstractAsyncNotification.java => AbstractAsyncRequestNotification.java} (84%) create mode 100644 src/main/java/com/hierynomus/smbj/event/AbstractAsyncResponseNotification.java create mode 100644 src/main/java/com/hierynomus/smbj/event/AsyncRequestMessageIdNotification.java create mode 100644 src/main/java/com/hierynomus/smbj/event/AsyncRequestNotification.java create mode 100644 src/main/java/com/hierynomus/smbj/event/AsyncResponseNotification.java diff --git a/src/it/groovy/com/hierynomus/smbj/SMB2FileIntegrationTest.groovy b/src/it/groovy/com/hierynomus/smbj/SMB2FileIntegrationTest.groovy index 47a47da8..c5a90789 100644 --- a/src/it/groovy/com/hierynomus/smbj/SMB2FileIntegrationTest.groovy +++ b/src/it/groovy/com/hierynomus/smbj/SMB2FileIntegrationTest.groovy @@ -18,6 +18,7 @@ package com.hierynomus.smbj import com.hierynomus.msdtyp.AccessMask import com.hierynomus.mserref.NtStatus import com.hierynomus.mssmb2.SMB2CreateDisposition +import com.hierynomus.mssmb2.SMB2FileId import com.hierynomus.mssmb2.SMB2OplockLevel import com.hierynomus.mssmb2.SMB2ShareAccess import com.hierynomus.mssmb2.SMBApiException @@ -206,13 +207,7 @@ class SMB2FileIntegrationTest extends Specification { @Override void handleAsyncCreateResponseNotification( AsyncCreateResponseNotification asyncCreateResponseNotification) { - def createResponseFuture = asyncCreateResponseNotification.future - def createResponse - try { - createResponse = createResponseFuture.get() - } catch (Throwable t) { - throw new IllegalStateException("Unable to get create response", t) - } + def createResponse = asyncCreateResponseNotification.createResponse def getPath = messageIdPathMap.remove(createResponse.header.messageId) if(getPath == null) { System.out.println("Could not find path in map. Should not related to async create, ignored.") @@ -262,7 +257,7 @@ class SMB2FileIntegrationTest extends Specification { def messageIdPathMap = new ConcurrentHashMap() // Should call async listener, just using hashmap as dummy in test case def messageIdDiskEntryMap = new ConcurrentHashMap() - def fileIdDiskEntryMap = new ConcurrentHashMap() + def fileIdDiskEntryMap = new ConcurrentHashMap() def succeedBreakToLevel2 = new AtomicBoolean(false) def oplockBreakAcknowledgmentResponseSucceed = new AtomicBoolean(false) share.setNotificationHandler( new AbstractNotificationHandler() { @@ -270,13 +265,7 @@ class SMB2FileIntegrationTest extends Specification { @Override void handleAsyncCreateResponseNotification( AsyncCreateResponseNotification asyncCreateResponseNotification) { - def createResponseFuture = asyncCreateResponseNotification.future - def createResponse - try { - createResponse = createResponseFuture.get() - } catch (Throwable t) { - throw new IllegalStateException("Unable to get create response", t) - } + def createResponse = asyncCreateResponseNotification.createResponse def getPath = messageIdPathMap.remove(createResponse.header.messageId) if(getPath == null) { System.out.println("Could not find path in map. Should not related to async create, ignored.") @@ -292,14 +281,14 @@ class SMB2FileIntegrationTest extends Specification { if(diskEntry != null) { // Should call async listener, just calling dummy in test case messageIdDiskEntryMap.put(createResponse.header.messageId, diskEntry) - fileIdDiskEntryMap.put(diskEntry.fileId.toHexString(), diskEntry) + fileIdDiskEntryMap.put(diskEntry.fileId, diskEntry) } } @Override void handleOplockBreakNotification(OplockBreakNotification oplockBreakNotification) { def oplockBreakLevel = oplockBreakNotification.oplockLevel - def getDiskEntry = fileIdDiskEntryMap.get(oplockBreakNotification.fileId.toHexString()) + def getDiskEntry = fileIdDiskEntryMap.get(oplockBreakNotification.fileId) if(getDiskEntry == null) { throw new IllegalStateException("Unable to get corresponding diskEntry!") } diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreak.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreak.java index 69c2c9a6..8318b482 100644 --- a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreak.java +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreak.java @@ -30,12 +30,12 @@ protected SMB2OplockBreak() { super(); } - protected SMB2OplockBreak(int structureSize, SMB2Dialect dialect, SMB2MessageCommandCode messageType, long sessionId) { - super(structureSize, dialect, messageType, sessionId); + protected SMB2OplockBreak(int structureSize, SMB2Dialect dialect, long sessionId) { + super(structureSize, dialect, SMB2MessageCommandCode.SMB2_OPLOCK_BREAK, sessionId); } - protected SMB2OplockBreak(int structureSize, SMB2Dialect dialect, SMB2MessageCommandCode messageType, long sessionId, long treeId) { - super(structureSize, dialect, messageType, sessionId, treeId); + protected SMB2OplockBreak(int structureSize, SMB2Dialect dialect, long sessionId, long treeId) { + super(structureSize, dialect, SMB2MessageCommandCode.SMB2_OPLOCK_BREAK, sessionId, treeId); } public SMB2OplockBreakLevel getOplockLevel() { diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgment.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgment.java index a4c61780..738b5c9a 100644 --- a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgment.java +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgment.java @@ -27,7 +27,7 @@ public class SMB2OplockBreakAcknowledgment extends SMB2OplockBreak { public SMB2OplockBreakAcknowledgment(SMB2Dialect negotiatedDialect, long sessionId, long treeId, SMB2OplockBreakLevel oplockLevel, SMB2FileId fileId) { - super(24, negotiatedDialect, SMB2MessageCommandCode.SMB2_OPLOCK_BREAK, sessionId, treeId); + super(24, negotiatedDialect, sessionId, treeId); this.oplockLevel = oplockLevel; this.fileId = fileId; } diff --git a/src/main/java/com/hierynomus/protocol/commons/concurrent/TaskQueue.java b/src/main/java/com/hierynomus/protocol/commons/concurrent/TaskQueue.java index 49cad962..22fb7e5a 100644 --- a/src/main/java/com/hierynomus/protocol/commons/concurrent/TaskQueue.java +++ b/src/main/java/com/hierynomus/protocol/commons/concurrent/TaskQueue.java @@ -13,108 +13,69 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Copyright (c) 2011-2017 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ -// Modified: Changed the package package com.hierynomus.protocol.commons.concurrent; -// Modified: Changed Logger import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.LinkedList; +import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; -/** - * A task queue that always run all tasks in order. The executor to run the tasks is passed when - * the tasks when the tasks are executed, this executor is not guaranteed to be used, as if several - * tasks are queued, the original thread will be used. - * - * More specifically, any call B to the {@link #execute(Runnable, Executor)} method that happens-after another call A to the - * same method, will result in B's task running after A's. - * - * @author David Lloyd - * @author Tim Fox - * @author Julien Viet - */ -public class TaskQueue { - - // Modified: Changed Logger - static final Logger log = LoggerFactory.getLogger(TaskQueue.class); +public class TaskQueue implements Executor { - private static class Task { + private final Logger logger; + private final Executor executor; - private final Runnable runnable; - private final Executor exec; + private final ConcurrentLinkedDeque queue = new ConcurrentLinkedDeque<>(); + private final AtomicBoolean isIdle = new AtomicBoolean(true); - public Task(Runnable runnable, Executor exec) { - this.runnable = runnable; - this.exec = exec; - } + public TaskQueue(Executor executor) { + this(executor, LoggerFactory.getLogger(TaskQueue.class)); } - // @protectedby tasks - private final LinkedList tasks = new LinkedList<>(); - - // @protectedby tasks - private Executor current; - - private final Runnable runner; - - public TaskQueue() { - // Modified: Changed from Java 8 Lambda Sytle to Java 7 Style - runner = new Runnable() { - @Override - public void run() { - TaskQueue.this.run(); - } - }; + public TaskQueue(Executor executor, Logger logger) { + this.executor = executor; + this.logger = logger; } - private void run() { - for (; ; ) { - final Task task; - synchronized (tasks) { - task = tasks.poll(); - if (task == null) { - current = null; - return; - } - if (task.exec != current) { - tasks.addFirst(task); - task.exec.execute(runner); - current = task.exec; - return; - } - } + private synchronized void taskFinished() { + final Runnable nextTask = queue.poll(); + if (nextTask != null) { try { - task.runnable.run(); + executor.execute(nextTask); } catch (Throwable t) { - log.error("Caught unexpected Throwable", t); + logger.error("Caught unexpected Throwable", t); + } finally { + taskFinished(); } + } else { + // the last task in queue is finished + isIdle.compareAndSet(false, true); } - }; + } - /** - * Run a task. - * - * @param task the task to run. - */ - public void execute(Runnable task, Executor executor) { - synchronized (tasks) { - tasks.add(new Task(task, executor)); - if (current == null) { - current = executor; - executor.execute(runner); + @Override + public synchronized void execute(final Runnable command) { + if (command == null) { + throw new NullPointerException("adding null task to task queue!"); + } + + final Runnable customTask = new Runnable() { + @Override + public void run() { + try { + command.run(); + } catch (Throwable ignored) { + logger.error("Caught unexpected Throwable", ignored); + } + taskFinished(); } + }; + + queue.add(customTask); + if (isIdle.compareAndSet(true, false)) { + executor.execute(queue.poll()); } } } diff --git a/src/main/java/com/hierynomus/smbj/SMBClient.java b/src/main/java/com/hierynomus/smbj/SMBClient.java index 1f34d830..e29f18f8 100644 --- a/src/main/java/com/hierynomus/smbj/SMBClient.java +++ b/src/main/java/com/hierynomus/smbj/SMBClient.java @@ -45,7 +45,7 @@ public class SMBClient implements Closeable { private SmbConfig config; - private SMBEventBus bus; + private SMBEventBus clientGlobalBus; private PathResolver pathResolver; public SMBClient() { @@ -56,10 +56,10 @@ public SMBClient(SmbConfig config) { this(config, new SMBEventBus()); } - public SMBClient(SmbConfig config, SMBEventBus bus) { + public SMBClient(SmbConfig config, SMBEventBus clientGlobalBus) { this.config = config; - this.bus = bus; - bus.subscribe(this); + this.clientGlobalBus = clientGlobalBus; + clientGlobalBus.subscribe(this); this.pathResolver = new SymlinkPathResolver(PathResolver.LOCAL); if (config.isDfsEnabled()) { this.pathResolver = new DFSPathResolver(this.pathResolver); @@ -98,7 +98,7 @@ private Connection getEstablishedOrConnect(String hostname, int port) throws IOE String hostPort = hostname + ":" + port; Connection cachedConnection = connectionTable.get(hostPort); if (cachedConnection == null || !cachedConnection.isConnected()) { - Connection connection = new Connection(config, this, bus); + Connection connection = new Connection(config, this, clientGlobalBus); try { connection.connect(hostname, port); } catch (IOException e) { diff --git a/src/main/java/com/hierynomus/smbj/connection/Connection.java b/src/main/java/com/hierynomus/smbj/connection/Connection.java index 9c9dbc01..fe78dfe3 100644 --- a/src/main/java/com/hierynomus/smbj/connection/Connection.java +++ b/src/main/java/com/hierynomus/smbj/connection/Connection.java @@ -34,15 +34,16 @@ import com.hierynomus.smbj.auth.AuthenticationContext; import com.hierynomus.smbj.auth.Authenticator; import com.hierynomus.smbj.common.SMBRuntimeException; -import com.hierynomus.smbj.common.SmbPath; import com.hierynomus.smbj.event.AsyncCreateRequestNotification; import com.hierynomus.smbj.event.AsyncCreateResponseNotification; +import com.hierynomus.smbj.event.AsyncRequestMessageIdNotification; import com.hierynomus.smbj.event.ConnectionClosed; import com.hierynomus.smbj.event.OplockBreakNotification; import com.hierynomus.smbj.event.SMBEventBus; import com.hierynomus.smbj.event.SessionLoggedOff; import com.hierynomus.smbj.event.handler.MessageIdCallback; import com.hierynomus.smbj.session.Session; +import com.hierynomus.smbj.share.DiskShare; import com.hierynomus.spnego.NegTokenInit; import com.hierynomus.spnego.SpnegoException; import net.engio.mbassy.listener.Handler; @@ -88,24 +89,25 @@ public SMBClient getClient() { private SmbConfig config; private TransportLayer> transport; - private final SMBEventBus bus; + private final SMBEventBus clientGlobalBus; + private final SMBEventBus connectionPrivateBus = new SMBEventBus(); private final ReentrantLock lock = new ReentrantLock(); private int remotePort; - public Connection(SmbConfig config, SMBClient client, SMBEventBus bus) { + public Connection(SmbConfig config, SMBClient client, SMBEventBus clientGlobalBus) { this.config = config; this.client = client; this.transport = config.getTransportLayerFactory().createTransportLayer(new PacketHandlers<>(new SMBPacketSerializer(), this, converter), config); - this.bus = bus; - bus.subscribe(this); + this.clientGlobalBus = clientGlobalBus; + connectionPrivateBus.subscribe(this); } public Connection(Connection connection) { this.client = connection.client; this.config = connection.config; this.transport = connection.transport; - this.bus = connection.bus; - bus.subscribe(this); + this.clientGlobalBus = connection.clientGlobalBus; + connectionPrivateBus.subscribe(this); } public void connect(String hostname, int port) throws IOException { @@ -147,7 +149,7 @@ public void close(boolean force) throws IOException { } finally { transport.disconnect(); logger.info("Closed connection to {}", getRemoteHostname()); - bus.publish(new ConnectionClosed(remoteName, remotePort)); + clientGlobalBus.publish(new ConnectionClosed(remoteName, remotePort)); } } @@ -205,7 +207,7 @@ public Session authenticate(AuthenticationContext authContext) { } private Session getSession(AuthenticationContext authContext) { - return new Session(this, authContext, bus, client.getPathResolver(), config.getSecurityProvider()); + return new Session(this, authContext, connectionPrivateBus, client.getPathResolver(), config.getSecurityProvider()); } private byte[] processAuthenticationToken(Authenticator authenticator, AuthenticationContext authContext, byte[] inputToken, Session session) throws IOException { @@ -287,12 +289,20 @@ public Future send(SMB2Packet packet, MessageIdCallbac long messageId = packet.getHeader().getMessageId(); - if(messageIdCallback != null) { + if (messageIdCallback != null) { messageIdCallback.callback(messageId); } - if(packet.getHeader().getMessage() == SMB2MessageCommandCode.SMB2_CREATE) { - bus.publish(new AsyncCreateRequestNotification( + if (DiskShare.asyncSupport.contains(packet.getHeader().getMessage())) { + connectionPrivateBus.publish(new AsyncRequestMessageIdNotification( + packet.getHeader().getSessionId(), + packet.getHeader().getTreeId(), + messageId) + ); + } + + if (packet.getHeader().getMessage() == SMB2MessageCommandCode.SMB2_CREATE) { + connectionPrivateBus.publish(new AsyncCreateRequestNotification( packet.getHeader().getSessionId(), packet.getHeader().getTreeId(), messageId) @@ -407,10 +417,10 @@ public void handle(SMBPacket uncheckedPacket) throws TransportException { // If the MessageId field of the SMB2 header of the response is 0xFFFFFFFFFFFFFFFF, // this MUST be // processed as an oplock break indication. - if(packet instanceof SMB2OplockBreakNotification) { + if (packet instanceof SMB2OplockBreakNotification) { SMB2OplockBreakNotification oplockBreakNotification = (SMB2OplockBreakNotification)packet; logger.debug("Received SMB2OplockBreakNotification Packet for FileId {} with {}", oplockBreakNotification.getFileId(), oplockBreakNotification.getOplockLevel()); - bus.publish(new OplockBreakNotification( + connectionPrivateBus.publish(new OplockBreakNotification( oplockBreakNotification.getOplockLevel(), oplockBreakNotification.getFileId() )); @@ -457,11 +467,9 @@ public void handle(SMBPacket uncheckedPacket) throws TransportException { } // Handing case for Oplock/Lease related issue - if(packet instanceof SMB2CreateResponse) { + if (packet instanceof SMB2CreateResponse) { SMB2CreateResponse smb2CreateResponse = (SMB2CreateResponse)packet; - bus.publish(new AsyncCreateResponseNotification( - smb2CreateResponse.getHeader().getSessionId(), - smb2CreateResponse.getHeader().getTreeId(), + connectionPrivateBus.publish(new AsyncCreateResponseNotification( messageId, smb2CreateResponse.getFileId(), smb2CreateResponse) diff --git a/src/main/java/com/hierynomus/smbj/event/AbstractAsyncNotification.java b/src/main/java/com/hierynomus/smbj/event/AbstractAsyncRequestNotification.java similarity index 84% rename from src/main/java/com/hierynomus/smbj/event/AbstractAsyncNotification.java rename to src/main/java/com/hierynomus/smbj/event/AbstractAsyncRequestNotification.java index 135c9ba3..7dbd0655 100644 --- a/src/main/java/com/hierynomus/smbj/event/AbstractAsyncNotification.java +++ b/src/main/java/com/hierynomus/smbj/event/AbstractAsyncRequestNotification.java @@ -15,12 +15,12 @@ */ package com.hierynomus.smbj.event; -public abstract class AbstractAsyncNotification implements AsyncNotification { +public abstract class AbstractAsyncRequestNotification implements AsyncRequestNotification { private long sessionId; private long treeId; - public AbstractAsyncNotification(long sessionId, long treeId) { + public AbstractAsyncRequestNotification(long sessionId, long treeId) { this.sessionId = sessionId; this.treeId = treeId; } @@ -35,4 +35,3 @@ public long getTreeId() { return treeId; } } - diff --git a/src/main/java/com/hierynomus/smbj/event/AbstractAsyncResponseNotification.java b/src/main/java/com/hierynomus/smbj/event/AbstractAsyncResponseNotification.java new file mode 100644 index 00000000..ef714f9a --- /dev/null +++ b/src/main/java/com/hierynomus/smbj/event/AbstractAsyncResponseNotification.java @@ -0,0 +1,30 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.smbj.event; + +public abstract class AbstractAsyncResponseNotification implements AsyncResponseNotification { + + private long messageId; + + public AbstractAsyncResponseNotification(long messageId) { + this.messageId = messageId; + } + + @Override + public long getMessageId() { + return messageId; + } +} diff --git a/src/main/java/com/hierynomus/smbj/event/AsyncCreateRequestNotification.java b/src/main/java/com/hierynomus/smbj/event/AsyncCreateRequestNotification.java index fd793213..0614bd31 100644 --- a/src/main/java/com/hierynomus/smbj/event/AsyncCreateRequestNotification.java +++ b/src/main/java/com/hierynomus/smbj/event/AsyncCreateRequestNotification.java @@ -20,7 +20,8 @@ /*** * Event for notifying the messageId to DiskShare Notification Handler */ -public class AsyncCreateRequestNotification extends AbstractAsyncNotification implements SMBEvent { +public class AsyncCreateRequestNotification extends AbstractAsyncRequestNotification + implements SMBEvent { private long messageId; diff --git a/src/main/java/com/hierynomus/smbj/event/AsyncCreateResponseNotification.java b/src/main/java/com/hierynomus/smbj/event/AsyncCreateResponseNotification.java index f9751084..34dbd820 100644 --- a/src/main/java/com/hierynomus/smbj/event/AsyncCreateResponseNotification.java +++ b/src/main/java/com/hierynomus/smbj/event/AsyncCreateResponseNotification.java @@ -21,24 +21,19 @@ /*** * Event for notifying the fileId and CreateResponseFuture to corresponding messageId on AysncCreate */ -public class AsyncCreateResponseNotification extends AbstractAsyncNotification implements SMBEvent { +public class AsyncCreateResponseNotification extends AbstractAsyncResponseNotification + implements SMBEvent { - private long messageId; private SMB2FileId fileId; private SMB2CreateResponse createResponse; - public AsyncCreateResponseNotification(long sessionId, long treeId, long messageId, - SMB2FileId fileId, SMB2CreateResponse createResponse) { - super(sessionId, treeId); - this.messageId = messageId; + public AsyncCreateResponseNotification(long messageId, SMB2FileId fileId, + SMB2CreateResponse createResponse) { + super(messageId); this.fileId = fileId; this.createResponse = createResponse; } - public long getMessageId() { - return messageId; - } - public SMB2FileId getFileId() { return fileId; } diff --git a/src/main/java/com/hierynomus/smbj/event/AsyncNotification.java b/src/main/java/com/hierynomus/smbj/event/AsyncNotification.java index 21f0e7fa..384e2ad6 100644 --- a/src/main/java/com/hierynomus/smbj/event/AsyncNotification.java +++ b/src/main/java/com/hierynomus/smbj/event/AsyncNotification.java @@ -21,7 +21,4 @@ */ public interface AsyncNotification { - long getSessionId(); - - long getTreeId(); } diff --git a/src/main/java/com/hierynomus/smbj/event/AsyncRequestMessageIdNotification.java b/src/main/java/com/hierynomus/smbj/event/AsyncRequestMessageIdNotification.java new file mode 100644 index 00000000..1f1630cc --- /dev/null +++ b/src/main/java/com/hierynomus/smbj/event/AsyncRequestMessageIdNotification.java @@ -0,0 +1,41 @@ + /* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.smbj.event; + +public class AsyncRequestMessageIdNotification implements SMBEvent { + + private long sessionId; + private long treeId; + private long messageId; + + public AsyncRequestMessageIdNotification(long sessionId, long treeId, long messageId) { + this.sessionId = sessionId; + this.treeId = treeId; + this.messageId = messageId; + } + + public long getSessionId() { + return sessionId; + } + + public long getTreeId() { + return treeId; + } + + public long getMessageId() { + return messageId; + } +} diff --git a/src/main/java/com/hierynomus/smbj/event/AsyncRequestNotification.java b/src/main/java/com/hierynomus/smbj/event/AsyncRequestNotification.java new file mode 100644 index 00000000..c8484fc6 --- /dev/null +++ b/src/main/java/com/hierynomus/smbj/event/AsyncRequestNotification.java @@ -0,0 +1,23 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.smbj.event; + +public interface AsyncRequestNotification extends AsyncNotification { + + long getSessionId(); + + long getTreeId(); +} diff --git a/src/main/java/com/hierynomus/smbj/event/AsyncResponseNotification.java b/src/main/java/com/hierynomus/smbj/event/AsyncResponseNotification.java new file mode 100644 index 00000000..227f6ebf --- /dev/null +++ b/src/main/java/com/hierynomus/smbj/event/AsyncResponseNotification.java @@ -0,0 +1,21 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.smbj.event; + +public interface AsyncResponseNotification extends AsyncNotification { + + long getMessageId(); +} diff --git a/src/main/java/com/hierynomus/smbj/event/OplockBreakNotification.java b/src/main/java/com/hierynomus/smbj/event/OplockBreakNotification.java index 8e33efc7..b4009238 100644 --- a/src/main/java/com/hierynomus/smbj/event/OplockBreakNotification.java +++ b/src/main/java/com/hierynomus/smbj/event/OplockBreakNotification.java @@ -21,14 +21,12 @@ /*** * Event for notifying the oplock break notification for corresponding fileId */ -public class OplockBreakNotification extends AbstractAsyncNotification implements SMBEvent { +public class OplockBreakNotification implements SMBEvent, AsyncNotification { private SMB2OplockBreakLevel oplockLevel; private SMB2FileId fileId; public OplockBreakNotification(SMB2OplockBreakLevel oplockLevel, SMB2FileId fileId) { - // will always getting 0 for sessionId and treeId for oplock break notification. - super(0L, 0L); this.oplockLevel = oplockLevel; this.fileId = fileId; } diff --git a/src/main/java/com/hierynomus/smbj/session/Session.java b/src/main/java/com/hierynomus/smbj/session/Session.java index 22c60711..11422210 100644 --- a/src/main/java/com/hierynomus/smbj/session/Session.java +++ b/src/main/java/com/hierynomus/smbj/session/Session.java @@ -57,7 +57,7 @@ public class Session implements AutoCloseable { private boolean encryptData; // SMB3.x private Connection connection; - private SMBEventBus bus; + private final SMBEventBus connectionPrivateBus; private final PathResolver pathResolver; private TreeConnectTable treeConnectTable = new TreeConnectTable(); private List nestedSessions = new ArrayList<>(); @@ -66,14 +66,14 @@ public class Session implements AutoCloseable { private boolean guest; private boolean anonymous; - public Session(Connection connection, AuthenticationContext userCredentials, SMBEventBus bus, PathResolver pathResolver, SecurityProvider securityProvider) { + public Session(Connection connection, AuthenticationContext userCredentials, SMBEventBus connectionPrivateBus, PathResolver pathResolver, SecurityProvider securityProvider) { this.connection = connection; this.userCredentials = userCredentials; - this.bus = bus; + this.connectionPrivateBus = connectionPrivateBus; this.pathResolver = pathResolver; this.packetSignatory = new PacketSignatory(connection.getNegotiatedProtocol().getDialect(), securityProvider); - if (bus != null) { - bus.subscribe(this); + if (connectionPrivateBus != null) { + connectionPrivateBus.subscribe(this); } } @@ -177,11 +177,11 @@ private Share connectTree(final String shareName) { } long treeId = response.getHeader().getTreeId(); - TreeConnect treeConnect = new TreeConnect(treeId, smbPath, this, response.getCapabilities(), connection, bus, response.getMaximalAccess()); + TreeConnect treeConnect = new TreeConnect(treeId, smbPath, this, response.getCapabilities(), connection, connectionPrivateBus, response.getMaximalAccess()); Share share; if (response.isDiskShare()) { - share = new DiskShare(smbPath, treeConnect, pathResolver, bus); + share = new DiskShare(smbPath, treeConnect, pathResolver, connectionPrivateBus); } else if (response.isNamedPipe()) { share = new PipeShare(smbPath, treeConnect); } else if (response.isPrinterShare()) { @@ -241,7 +241,7 @@ public void logoff() throws TransportException { throw new SMBApiException(response.getHeader(), "Could not logoff session <<" + sessionId + ">>"); } } finally { - bus.publish(new SessionLoggedOff(sessionId)); + connectionPrivateBus.publish(new SessionLoggedOff(sessionId)); } } diff --git a/src/main/java/com/hierynomus/smbj/share/DiskShare.java b/src/main/java/com/hierynomus/smbj/share/DiskShare.java index 435779ca..54c41cd0 100644 --- a/src/main/java/com/hierynomus/smbj/share/DiskShare.java +++ b/src/main/java/com/hierynomus/smbj/share/DiskShare.java @@ -37,6 +37,7 @@ import com.hierynomus.smbj.common.SmbPath; import com.hierynomus.smbj.event.AsyncCreateRequestNotification; import com.hierynomus.smbj.event.AsyncCreateResponseNotification; +import com.hierynomus.smbj.event.AsyncRequestMessageIdNotification; import com.hierynomus.smbj.event.OplockBreakNotification; import com.hierynomus.smbj.event.SMBEventBus; import com.hierynomus.smbj.event.handler.MessageIdCallback; @@ -69,34 +70,35 @@ import static com.hierynomus.mssmb2.SMB2CreateDisposition.FILE_OPEN; import static com.hierynomus.mssmb2.SMB2CreateOptions.FILE_DIRECTORY_FILE; import static com.hierynomus.mssmb2.SMB2CreateOptions.FILE_NON_DIRECTORY_FILE; +import static com.hierynomus.mssmb2.SMB2MessageCommandCode.SMB2_CREATE; import static com.hierynomus.mssmb2.SMB2ShareAccess.*; import static com.hierynomus.mssmb2.messages.SMB2QueryInfoRequest.SMB2QueryInfoType.SMB2_0_INFO_SECURITY; import static java.util.EnumSet.of; import static java.util.EnumSet.noneOf; public class DiskShare extends Share { + public static final EnumSet asyncSupport = EnumSet.of(SMB2_CREATE); private static final Logger logger = LoggerFactory.getLogger(DiskShare.class); private final PathResolver resolver; - private SMBEventBus bus; + private final SMBEventBus connectionPrivateBus; private NotificationHandler notificationHandler = null; private final ExecutorService notifyExecutor; private final boolean isCreatedNotifyExecutor; - private final TaskQueue taskQueue = new TaskQueue(); - // TODO: ensure the event is only used by one Connect instance + private final TaskQueue taskQueue; private final Set openedOplockFileId = Collections.newSetFromMap(new ConcurrentHashMap()); - // TODO: Implement a internal centralize messageId callback to record all implemented notificationHandler MessageId - private final Set createRequestMessageId = Collections.newSetFromMap(new ConcurrentHashMap()); + // Only add the messageId to Set when the operation is on the asyncSupport Set. Must remove when receive the corresponding AsyncResponse. + private final Set asyncOperationMessageId = Collections.newSetFromMap(new ConcurrentHashMap()); - public DiskShare(SmbPath smbPath, TreeConnect treeConnect, PathResolver pathResolver, SMBEventBus bus) { + public DiskShare(SmbPath smbPath, TreeConnect treeConnect, PathResolver pathResolver, SMBEventBus connectionPrivateBus) { super(smbPath, treeConnect); this.resolver = pathResolver; - this.bus = bus; - if (bus != null) { - bus.subscribe(this); + this.connectionPrivateBus = connectionPrivateBus; + if (connectionPrivateBus != null) { + connectionPrivateBus.subscribe(this); } ExecutorService executorFromConfig = treeConnect.getConnection().getConfig().getNotifyExecutorService(); - if(executorFromConfig != null) { + if (executorFromConfig != null) { notifyExecutor = executorFromConfig; isCreatedNotifyExecutor = false; } else { @@ -111,18 +113,19 @@ public Thread newThread(Runnable r) { }); isCreatedNotifyExecutor = true; } + this.taskQueue = new TaskQueue(notifyExecutor, logger); } @Override public void close() throws IOException { super.close(); - if(isCreatedNotifyExecutor) { + if (isCreatedNotifyExecutor) { // cleanup for executor notifyExecutor.shutdown(); } // cleanup for set openedOplockFileId.clear(); - createRequestMessageId.clear(); + asyncOperationMessageId.clear(); } public DiskEntry open(String path, Set accessMask, Set attributes, Set shareAccesses, SMB2CreateDisposition createDisposition, Set createOptions) { @@ -596,6 +599,26 @@ public void setNotificationHandler(NotificationHandler handler) { this.notificationHandler = handler; } + /*** + * Record the messageId for the Async Operation. + * + * @param asyncRequestMessageIdNotification messageId requires handle Async Response for specific sessionId and treeId. + */ + @Handler + @SuppressWarnings("unused") + private void setMessageIdForSupportedAsyncOperation(final AsyncRequestMessageIdNotification asyncRequestMessageIdNotification) { + try { + if (asyncRequestMessageIdNotification.getSessionId() == this.sessionId + && asyncRequestMessageIdNotification.getTreeId() == this.treeId) { + // add the messageId to Set if Async Request is sending by this DiskShare + asyncOperationMessageId.add(asyncRequestMessageIdNotification.getMessageId()); + } + } catch (Throwable t) { + logger.error("Handling setMessageIdForSupportedAsyncOperation error occur : ", t); + throw t; + } + } + /*** * Handler for handing the oplock break notification event from server. 3.2.5.19 Receiving an SMB2 OPLOCK_BREAK Notification. * @@ -608,24 +631,27 @@ private void oplockBreakNotification(final OplockBreakNotification oplockBreakNo final SMB2FileId fileId = oplockBreakNotification.getFileId(); final SMB2OplockBreakLevel oplockLevel = oplockBreakNotification.getOplockLevel(); - logger.debug("FileId {} received OplockBreakNotification, Oplock level {}", fileId, oplockLevel); - - if(notificationHandler != null) { - // Preventing the improper use of handler (holding the thread). if holding thread, timeout exception will be throw. - if (openedOplockFileId.contains(fileId)) { + // Check should this DiskShare handle this oplock break notification. If not, just ignore. + if (openedOplockFileId.contains(fileId)) { + logger.debug("FileId {} received OplockBreakNotification, Oplock level {}", fileId, + oplockLevel); + if (notificationHandler != null) { + // Preventing the improper use of handler (holding the thread). if holding thread, timeout exception will be throw. // submit to taskQueue only when this DiskShare opened a handle with this fileId. Otherwise, ignore it. taskQueue.execute(new Runnable() { @Override public void run() { notificationHandler.handleOplockBreakNotification(oplockBreakNotification); } - }, notifyExecutor); + }); + } else { + logger.warn( + "FileId {}, NotificationHandler not exist to handle Oplock Break. On treeId = {}", + fileId, this.treeId); + throw new IllegalStateException( + "NotificationHandler not exist to handle Oplock Break."); } - }else { - logger.warn("FileId {}, NotificationHandler not exist to handle Oplock Break.", fileId); - throw new IllegalStateException("NotificationHandler not exist to handle Oplock Break."); } - } catch (Throwable t) { logger.error("Handling oplockBreakNotification error occur : ", t); throw t; @@ -641,26 +667,23 @@ public void run() { @SuppressWarnings("unused") private void createRequestNotification(final AsyncCreateRequestNotification asyncCreateRequestNotification) { try { - if(notificationHandler != null) { - // Preventing the improper use of handler (holding the thread). if holding thread, timeout exception will be throw. - // Checking treeId can always map a DiskShare for AsyncCreateRequestNotification, because this happens before sending message. - if(asyncCreateRequestNotification.getSessionId() == this.sessionId - && asyncCreateRequestNotification.getTreeId() == this.treeId) { - // add the messageId to Set if createRequest is sending by this DiskShare - createRequestMessageId.add(asyncCreateRequestNotification.getMessageId()); - + // Checking treeId can always map a DiskShare for AsyncCreateRequestNotification, because this happens before sending message. + if (asyncCreateRequestNotification.getSessionId() == this.sessionId + && asyncCreateRequestNotification.getTreeId() == this.treeId) { + if (notificationHandler != null) { + // Preventing the improper use of handler (holding the thread). if holding thread, timeout exception will be throw. // submit to taskQueue only when sessionId and treeId match. Otherwise, ignore it. taskQueue.execute(new Runnable() { @Override public void run() { notificationHandler.handleAsyncCreateRequestNotification(asyncCreateRequestNotification); } - }, notifyExecutor); - }else { - logger.debug("asyncCreateRequestNotification ignored. this.treeId = " + this.treeId + ", notification.getTreeId() = " + asyncCreateRequestNotification.getTreeId()); + }); + } else { + logger.debug("NotificationHandler not exist to handle asyncCreateRequestNotification. On treeId = {}", this.treeId); } - }else { - logger.debug("NotificationHandler not exist to handle asyncCreateRequestNotification"); + } else { + logger.debug("asyncCreateRequestNotification ignored. this.treeId = {}, notification.getTreeId() = {}", this.treeId, asyncCreateRequestNotification.getTreeId()); } } catch (Throwable t) { logger.error("Handling createRequestNotification error occur : ", t); @@ -680,23 +703,24 @@ public void run() { @SuppressWarnings("unused") private void createResponseNotification(final AsyncCreateResponseNotification asyncCreateResponseNotification) { try { - if(notificationHandler != null) { - // Preventing the improper use of handler (holding the thread). if holding thread, timeout exception will be throw. - boolean shouldHandle = createRequestMessageId.remove(asyncCreateResponseNotification.getMessageId()); - if(shouldHandle) { + // No matter the notificationHandler is set or not. Always try to remove the messageId from the Set. + boolean shouldHandle = asyncOperationMessageId.remove(asyncCreateResponseNotification.getMessageId()); + // Check should this DiskShare handle the create response. If not, just ignore. + if (shouldHandle) { + if (notificationHandler != null) { + // Preventing the improper use of handler (holding the thread). if holding thread, timeout exception will be throw. // submit to taskQueue only if createRequest is sent out by this DiskShare. Otherwise, ignore it. taskQueue.execute(new Runnable() { @Override public void run() { - notificationHandler.handleAsyncCreateResponseNotification( - asyncCreateResponseNotification); + notificationHandler.handleAsyncCreateResponseNotification(asyncCreateResponseNotification); } - }, notifyExecutor); - }else { - logger.debug("asyncCreateResponseNotification ignored. MessageId = " + asyncCreateResponseNotification.getMessageId() + ", is not handled by this.treeId = " + this.treeId); + }); + } else { + logger.debug("NotificationHandler not exist to handle asyncCreateResponseNotification. On treeId = {}", this.treeId); } - }else { - logger.debug("NotificationHandler not exist to handle asyncCreateResponseNotification"); + } else { + logger.debug("asyncCreateResponseNotification ignored. MessageId = {}, is not handled by this.treeId = {}", asyncCreateResponseNotification.getMessageId(), this.treeId); } } catch (Throwable t) { logger.error("Handling createResponseNotification error occur : ", t); diff --git a/src/main/java/com/hierynomus/smbj/share/TreeConnect.java b/src/main/java/com/hierynomus/smbj/share/TreeConnect.java index 04672a63..678332ed 100644 --- a/src/main/java/com/hierynomus/smbj/share/TreeConnect.java +++ b/src/main/java/com/hierynomus/smbj/share/TreeConnect.java @@ -42,16 +42,16 @@ public class TreeConnect { private Session session; private final Set capabilities; private Connection connection; - private final SMBEventBus bus; + private final SMBEventBus connectionPrivateBus; private final Set maximalAccess; - public TreeConnect(long treeId, SmbPath smbPath, Session session, Set capabilities, Connection connection, SMBEventBus bus, Set maximalAccess) { + public TreeConnect(long treeId, SmbPath smbPath, Session session, Set capabilities, Connection connection, SMBEventBus connectionPrivateBus, Set maximalAccess) { this.treeId = treeId; this.smbPath = smbPath; this.session = session; this.capabilities = capabilities; this.connection = connection; - this.bus = bus; + this.connectionPrivateBus = connectionPrivateBus; this.maximalAccess = maximalAccess; } @@ -68,7 +68,7 @@ void close() throws TransportException { throw new SMBApiException(smb2Packet.getHeader(), "Error closing connection to " + smbPath); } } finally { - bus.publish(new TreeDisconnected(session.getSessionId(), treeId)); + connectionPrivateBus.publish(new TreeDisconnected(session.getSessionId(), treeId)); } } From a3fed234513a709d159435849fd0b8d73ba2497d Mon Sep 17 00:00:00 2001 From: yin19941005 Date: Tue, 17 Jul 2018 18:31:53 +0800 Subject: [PATCH 09/12] Change TaskQueue to interface and allow set by config. Remove set executorService in config. --- .../SingleThreadExecutorTaskQueue.java | 33 +++++++++ .../commons/concurrent/TaskQueue.java | 72 +++---------------- .../java/com/hierynomus/smbj/SmbConfig.java | 16 ++--- .../com/hierynomus/smbj/share/DiskShare.java | 32 +++------ 4 files changed, 61 insertions(+), 92 deletions(-) create mode 100644 src/main/java/com/hierynomus/protocol/commons/concurrent/SingleThreadExecutorTaskQueue.java diff --git a/src/main/java/com/hierynomus/protocol/commons/concurrent/SingleThreadExecutorTaskQueue.java b/src/main/java/com/hierynomus/protocol/commons/concurrent/SingleThreadExecutorTaskQueue.java new file mode 100644 index 00000000..2eb0d942 --- /dev/null +++ b/src/main/java/com/hierynomus/protocol/commons/concurrent/SingleThreadExecutorTaskQueue.java @@ -0,0 +1,33 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hierynomus.protocol.commons.concurrent; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class SingleThreadExecutorTaskQueue implements TaskQueue{ + + private final ExecutorService executorService = Executors.newSingleThreadExecutor(); + + @Override + public void execute(Runnable task) { + executorService.execute(task); + } + + public void close() { + executorService.shutdown(); + } +} diff --git a/src/main/java/com/hierynomus/protocol/commons/concurrent/TaskQueue.java b/src/main/java/com/hierynomus/protocol/commons/concurrent/TaskQueue.java index 22fb7e5a..84446c03 100644 --- a/src/main/java/com/hierynomus/protocol/commons/concurrent/TaskQueue.java +++ b/src/main/java/com/hierynomus/protocol/commons/concurrent/TaskQueue.java @@ -15,67 +15,15 @@ */ package com.hierynomus.protocol.commons.concurrent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.ConcurrentLinkedDeque; -import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicBoolean; - -public class TaskQueue implements Executor { - - private final Logger logger; - private final Executor executor; - - private final ConcurrentLinkedDeque queue = new ConcurrentLinkedDeque<>(); - private final AtomicBoolean isIdle = new AtomicBoolean(true); - - public TaskQueue(Executor executor) { - this(executor, LoggerFactory.getLogger(TaskQueue.class)); - } - - public TaskQueue(Executor executor, Logger logger) { - this.executor = executor; - this.logger = logger; - } - - private synchronized void taskFinished() { - final Runnable nextTask = queue.poll(); - if (nextTask != null) { - try { - executor.execute(nextTask); - } catch (Throwable t) { - logger.error("Caught unexpected Throwable", t); - } finally { - taskFinished(); - } - } else { - // the last task in queue is finished - isIdle.compareAndSet(false, true); - } - } - - @Override - public synchronized void execute(final Runnable command) { - if (command == null) { - throw new NullPointerException("adding null task to task queue!"); - } - - final Runnable customTask = new Runnable() { - @Override - public void run() { - try { - command.run(); - } catch (Throwable ignored) { - logger.error("Caught unexpected Throwable", ignored); - } - taskFinished(); - } - }; +/*** + * An object that executes submitted tasks in sequence. The TaskQueue MUST guarantee all + * submitted tasks will execute in sequence. + */ +public interface TaskQueue { - queue.add(customTask); - if (isIdle.compareAndSet(true, false)) { - executor.execute(queue.poll()); - } - } + /*** + * execute the task. The implementation MUST guarantee the execution is in sequence. + * @param task runnable task + */ + void execute(Runnable task); } diff --git a/src/main/java/com/hierynomus/smbj/SmbConfig.java b/src/main/java/com/hierynomus/smbj/SmbConfig.java index 1450a443..e395ce14 100644 --- a/src/main/java/com/hierynomus/smbj/SmbConfig.java +++ b/src/main/java/com/hierynomus/smbj/SmbConfig.java @@ -17,6 +17,7 @@ import com.hierynomus.mssmb2.SMB2Dialect; import com.hierynomus.protocol.commons.Factory; +import com.hierynomus.protocol.commons.concurrent.TaskQueue; import com.hierynomus.protocol.commons.socket.ProxySocketFactory; import com.hierynomus.security.SecurityProvider; import com.hierynomus.security.bc.BCSecurityProvider; @@ -31,7 +32,6 @@ import javax.net.SocketFactory; import java.security.SecureRandom; import java.util.*; -import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; public final class SmbConfig { @@ -73,7 +73,7 @@ public final class SmbConfig { private int transactBufferSize; private TransportLayerFactory> transportLayerFactory; private long transactTimeout; - private ExecutorService notifyExecutor; + private TaskQueue taskQueue; private int soTimeout; @@ -97,7 +97,7 @@ public static Builder builder() { // order is important. The authenticators listed first will be selected .withAuthenticators(getDefaultAuthenticators()) .withTimeout(DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_UNIT) - .withNotifyExecutorService(null); + .withTaskQueue(null); } @@ -149,7 +149,7 @@ private SmbConfig(SmbConfig other) { transportLayerFactory = other.transportLayerFactory; soTimeout = other.soTimeout; useMultiProtocolNegotiate = other.useMultiProtocolNegotiate; - notifyExecutor = other.notifyExecutor; + taskQueue = other.taskQueue; } public Random getRandomProvider() { @@ -224,8 +224,8 @@ public SocketFactory getSocketFactory() { return socketFactory; } - public ExecutorService getNotifyExecutorService() { - return notifyExecutor; + public TaskQueue getTaskQueue() { + return taskQueue; } public static class Builder { @@ -407,8 +407,8 @@ public Builder withMultiProtocolNegotiate(boolean useMultiProtocolNegotiate) { return this; } - public Builder withNotifyExecutorService(ExecutorService notifyExecutor) { - config.notifyExecutor = notifyExecutor; + public Builder withTaskQueue(TaskQueue taskQueue) { + config.taskQueue = taskQueue; return this; } } diff --git a/src/main/java/com/hierynomus/smbj/share/DiskShare.java b/src/main/java/com/hierynomus/smbj/share/DiskShare.java index 54c41cd0..3535eb26 100644 --- a/src/main/java/com/hierynomus/smbj/share/DiskShare.java +++ b/src/main/java/com/hierynomus/smbj/share/DiskShare.java @@ -29,6 +29,7 @@ import com.hierynomus.protocol.commons.EnumWithValue; import com.hierynomus.protocol.commons.buffer.Buffer; import com.hierynomus.protocol.commons.buffer.Endian; +import com.hierynomus.protocol.commons.concurrent.SingleThreadExecutorTaskQueue; import com.hierynomus.protocol.commons.concurrent.TaskQueue; import com.hierynomus.protocol.transport.TransportException; import com.hierynomus.smb.SMBBuffer; @@ -57,10 +58,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.concurrent.ThreadFactory; import static com.hierynomus.msdtyp.AccessMask.*; import static com.hierynomus.mserref.NtStatus.*; @@ -83,8 +81,7 @@ public class DiskShare extends Share { private final SMBEventBus connectionPrivateBus; private NotificationHandler notificationHandler = null; - private final ExecutorService notifyExecutor; - private final boolean isCreatedNotifyExecutor; + private final boolean isCreatedTaskQueue; private final TaskQueue taskQueue; private final Set openedOplockFileId = Collections.newSetFromMap(new ConcurrentHashMap()); // Only add the messageId to Set when the operation is on the asyncSupport Set. Must remove when receive the corresponding AsyncResponse. @@ -97,31 +94,22 @@ public DiskShare(SmbPath smbPath, TreeConnect treeConnect, PathResolver pathReso if (connectionPrivateBus != null) { connectionPrivateBus.subscribe(this); } - ExecutorService executorFromConfig = treeConnect.getConnection().getConfig().getNotifyExecutorService(); - if (executorFromConfig != null) { - notifyExecutor = executorFromConfig; - isCreatedNotifyExecutor = false; + TaskQueue taskQueueFromConfig = treeConnect.getConnection().getConfig().getTaskQueue(); + if (taskQueueFromConfig != null) { + taskQueue = taskQueueFromConfig; + isCreatedTaskQueue = false; } else { - notifyExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - Thread t = Executors.defaultThreadFactory().newThread(r); - t.setDaemon(true); - t.setName(DiskShare.super.smbPath.getShareName() + "-Thread-1"); - return t; - } - }); - isCreatedNotifyExecutor = true; + taskQueue = new SingleThreadExecutorTaskQueue(); + isCreatedTaskQueue = true; } - this.taskQueue = new TaskQueue(notifyExecutor, logger); } @Override public void close() throws IOException { super.close(); - if (isCreatedNotifyExecutor) { + if (isCreatedTaskQueue) { // cleanup for executor - notifyExecutor.shutdown(); + ((SingleThreadExecutorTaskQueue)taskQueue).close(); } // cleanup for set openedOplockFileId.clear(); From eedacd51b92cb44241250478a432063b0e758bbd Mon Sep 17 00:00:00 2001 From: yin19941005 Date: Fri, 10 Aug 2018 14:40:37 +0800 Subject: [PATCH 10/12] Merge SMB2_OPLOCK_BREAK_LEVEL to SMB2_OPLOCK_LEVEL. Base on SMB2 document, there are no SMB2_OPLOCK_BREAK_LEVEL. Merge to SMB2_OPLOCK_LEVEL and remove SMB2_OPLOCK_BREAK_LEVEL. --- .../mssmb2/SMB2OplockBreakLevel.java | 37 ------------------- .../mssmb2/messages/SMB2OplockBreak.java | 6 +-- .../SMB2OplockBreakAcknowledgment.java | 5 +-- .../SMB2OplockBreakServerResponse.java | 4 +- .../smbj/event/OplockBreakNotification.java | 8 ++-- .../com/hierynomus/smbj/share/DiskEntry.java | 4 +- .../com/hierynomus/smbj/share/DiskShare.java | 2 +- .../java/com/hierynomus/smbj/share/Share.java | 2 +- 8 files changed, 15 insertions(+), 53 deletions(-) delete mode 100644 src/main/java/com/hierynomus/mssmb2/SMB2OplockBreakLevel.java diff --git a/src/main/java/com/hierynomus/mssmb2/SMB2OplockBreakLevel.java b/src/main/java/com/hierynomus/mssmb2/SMB2OplockBreakLevel.java deleted file mode 100644 index 3262c2cc..00000000 --- a/src/main/java/com/hierynomus/mssmb2/SMB2OplockBreakLevel.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C)2016 - SMBJ Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.hierynomus.mssmb2; - -import com.hierynomus.protocol.commons.EnumWithValue; - -/** - * [MS-SMB2].pdf 2.2.23 SMB2 OPLOCK_BREAK Notification - OplockLevel - */ -public enum SMB2OplockBreakLevel implements EnumWithValue { - SMB2_OPLOCK_LEVEL_NONE(0x00L), - SMB2_OPLOCK_LEVEL_II(0x01L); - - private long value; - - SMB2OplockBreakLevel(long value) { - this.value = value; - } - - @Override - public long getValue() { - return value; - } -} diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreak.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreak.java index 8318b482..21f09c34 100644 --- a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreak.java +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreak.java @@ -18,12 +18,12 @@ import com.hierynomus.mssmb2.SMB2Dialect; import com.hierynomus.mssmb2.SMB2FileId; import com.hierynomus.mssmb2.SMB2MessageCommandCode; -import com.hierynomus.mssmb2.SMB2OplockBreakLevel; +import com.hierynomus.mssmb2.SMB2OplockLevel; import com.hierynomus.mssmb2.SMB2Packet; public abstract class SMB2OplockBreak extends SMB2Packet { - protected SMB2OplockBreakLevel oplockLevel; + protected SMB2OplockLevel oplockLevel; protected SMB2FileId fileId; protected SMB2OplockBreak() { @@ -38,7 +38,7 @@ protected SMB2OplockBreak(int structureSize, SMB2Dialect dialect, long sessionId super(structureSize, dialect, SMB2MessageCommandCode.SMB2_OPLOCK_BREAK, sessionId, treeId); } - public SMB2OplockBreakLevel getOplockLevel() { + public SMB2OplockLevel getOplockLevel() { return oplockLevel; } diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgment.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgment.java index 738b5c9a..8a649adc 100644 --- a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgment.java +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakAcknowledgment.java @@ -17,8 +17,7 @@ import com.hierynomus.mssmb2.SMB2Dialect; import com.hierynomus.mssmb2.SMB2FileId; -import com.hierynomus.mssmb2.SMB2MessageCommandCode; -import com.hierynomus.mssmb2.SMB2OplockBreakLevel; +import com.hierynomus.mssmb2.SMB2OplockLevel; import com.hierynomus.smb.SMBBuffer; /** @@ -26,7 +25,7 @@ */ public class SMB2OplockBreakAcknowledgment extends SMB2OplockBreak { - public SMB2OplockBreakAcknowledgment(SMB2Dialect negotiatedDialect, long sessionId, long treeId, SMB2OplockBreakLevel oplockLevel, SMB2FileId fileId) { + public SMB2OplockBreakAcknowledgment(SMB2Dialect negotiatedDialect, long sessionId, long treeId, SMB2OplockLevel oplockLevel, SMB2FileId fileId) { super(24, negotiatedDialect, sessionId, treeId); this.oplockLevel = oplockLevel; this.fileId = fileId; diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakServerResponse.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakServerResponse.java index 08fb1f11..e60f611a 100644 --- a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakServerResponse.java +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakServerResponse.java @@ -16,7 +16,7 @@ package com.hierynomus.mssmb2.messages; import com.hierynomus.mssmb2.SMB2FileId; -import com.hierynomus.mssmb2.SMB2OplockBreakLevel; +import com.hierynomus.mssmb2.SMB2OplockLevel; import com.hierynomus.protocol.commons.EnumWithValue; import com.hierynomus.protocol.commons.buffer.Buffer; import com.hierynomus.smb.SMBBuffer; @@ -25,7 +25,7 @@ public class SMB2OplockBreakServerResponse extends SMB2OplockBreak { @Override protected void readMessage(SMBBuffer buffer) throws Buffer.BufferException { buffer.readUInt16(); // StructureSize (2 bytes) - oplockLevel = EnumWithValue.EnumUtils.valueOf(buffer.readByte(), SMB2OplockBreakLevel.class, SMB2OplockBreakLevel.SMB2_OPLOCK_LEVEL_NONE); // OpLockLevel (1 byte) + oplockLevel = EnumWithValue.EnumUtils.valueOf(buffer.readByte(), SMB2OplockLevel.class, SMB2OplockLevel.SMB2_OPLOCK_LEVEL_NONE); // OpLockLevel (1 byte) buffer.readByte(); // Reserved (1 byte) buffer.skip(4); // Reserved2 (4 bytes) fileId = SMB2FileId.read(buffer); // FileId (16 bytes) diff --git a/src/main/java/com/hierynomus/smbj/event/OplockBreakNotification.java b/src/main/java/com/hierynomus/smbj/event/OplockBreakNotification.java index b4009238..578f535b 100644 --- a/src/main/java/com/hierynomus/smbj/event/OplockBreakNotification.java +++ b/src/main/java/com/hierynomus/smbj/event/OplockBreakNotification.java @@ -16,22 +16,22 @@ package com.hierynomus.smbj.event; import com.hierynomus.mssmb2.SMB2FileId; -import com.hierynomus.mssmb2.SMB2OplockBreakLevel; +import com.hierynomus.mssmb2.SMB2OplockLevel; /*** * Event for notifying the oplock break notification for corresponding fileId */ public class OplockBreakNotification implements SMBEvent, AsyncNotification { - private SMB2OplockBreakLevel oplockLevel; + private SMB2OplockLevel oplockLevel; private SMB2FileId fileId; - public OplockBreakNotification(SMB2OplockBreakLevel oplockLevel, SMB2FileId fileId) { + public OplockBreakNotification(SMB2OplockLevel oplockLevel, SMB2FileId fileId) { this.oplockLevel = oplockLevel; this.fileId = fileId; } - public SMB2OplockBreakLevel getOplockLevel() { + public SMB2OplockLevel getOplockLevel() { return oplockLevel; } diff --git a/src/main/java/com/hierynomus/smbj/share/DiskEntry.java b/src/main/java/com/hierynomus/smbj/share/DiskEntry.java index 39a67c25..a198ec9c 100644 --- a/src/main/java/com/hierynomus/smbj/share/DiskEntry.java +++ b/src/main/java/com/hierynomus/smbj/share/DiskEntry.java @@ -23,7 +23,7 @@ import com.hierynomus.msfscc.fileinformation.FileRenameInformation; import com.hierynomus.msfscc.fileinformation.FileSettableInformation; import com.hierynomus.mssmb2.SMB2FileId; -import com.hierynomus.mssmb2.SMB2OplockBreakLevel; +import com.hierynomus.mssmb2.SMB2OplockLevel; import com.hierynomus.mssmb2.SMBApiException; import com.hierynomus.mssmb2.messages.SMB2OplockBreakAcknowledgmentResponse; import com.hierynomus.protocol.transport.TransportException; @@ -198,7 +198,7 @@ public void closeSilently() { * @param oplockLevel the oplock break level after receiving the oplock break notification (current holding oplock level) * @return Server response to oplock break acknowledgment. 2.2.25 SMB2 OPLOCK_BREAK Response. */ - public SMB2OplockBreakAcknowledgmentResponse acknowledgeOplockBreak(SMB2OplockBreakLevel oplockLevel) { + public SMB2OplockBreakAcknowledgmentResponse acknowledgeOplockBreak(SMB2OplockLevel oplockLevel) { return share.sendOplockBreakAcknowledgment(fileId, oplockLevel); } } diff --git a/src/main/java/com/hierynomus/smbj/share/DiskShare.java b/src/main/java/com/hierynomus/smbj/share/DiskShare.java index 3535eb26..97d1c009 100644 --- a/src/main/java/com/hierynomus/smbj/share/DiskShare.java +++ b/src/main/java/com/hierynomus/smbj/share/DiskShare.java @@ -618,7 +618,7 @@ private void oplockBreakNotification(final OplockBreakNotification oplockBreakNo try { final SMB2FileId fileId = oplockBreakNotification.getFileId(); - final SMB2OplockBreakLevel oplockLevel = oplockBreakNotification.getOplockLevel(); + final SMB2OplockLevel oplockLevel = oplockBreakNotification.getOplockLevel(); // Check should this DiskShare handle this oplock break notification. If not, just ignore. if (openedOplockFileId.contains(fileId)) { logger.debug("FileId {} received OplockBreakNotification, Oplock level {}", fileId, diff --git a/src/main/java/com/hierynomus/smbj/share/Share.java b/src/main/java/com/hierynomus/smbj/share/Share.java index fa37ca8c..1c171da5 100644 --- a/src/main/java/com/hierynomus/smbj/share/Share.java +++ b/src/main/java/com/hierynomus/smbj/share/Share.java @@ -335,7 +335,7 @@ private Future ioctlAsync(SMB2FileId fileId, long ctlCode, bo return send(ioreq); } - SMB2OplockBreakAcknowledgmentResponse sendOplockBreakAcknowledgment(SMB2FileId fileId, SMB2OplockBreakLevel oplockLevel) { + SMB2OplockBreakAcknowledgmentResponse sendOplockBreakAcknowledgment(SMB2FileId fileId, SMB2OplockLevel oplockLevel) { SMB2OplockBreakAcknowledgment qreq = new SMB2OplockBreakAcknowledgment( dialect, sessionId, treeId, From e8517b60027e90b921a885d15fa7600f47eff2ab Mon Sep 17 00:00:00 2001 From: yin19941005 Date: Fri, 10 Aug 2018 14:55:00 +0800 Subject: [PATCH 11/12] Remove unused import --- .../com/hierynomus/mssmb2/messages/SMB2MessageConverter.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2MessageConverter.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2MessageConverter.java index 18c0bdc1..3d7768c8 100644 --- a/src/main/java/com/hierynomus/mssmb2/messages/SMB2MessageConverter.java +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2MessageConverter.java @@ -23,8 +23,6 @@ import com.hierynomus.smbj.common.Check; import com.hierynomus.smbj.common.SMBRuntimeException; -import java.util.Arrays; - public class SMB2MessageConverter implements PacketFactory { private SMB2OplockBreakFactory oplockBreakFactory = new SMB2OplockBreakFactory(); From cf27e09a29125041af913ff86d4aeb6296f4460b Mon Sep 17 00:00:00 2001 From: yin19941005 Date: Fri, 17 Aug 2018 18:33:43 +0800 Subject: [PATCH 12/12] Codacy Cleanup --- .../java/com/hierynomus/mssmb2/SMB2OplockLevel.java | 2 +- .../mssmb2/messages/SMB2OplockBreakFactory.java | 2 +- .../smbj/event/AsyncCreateRequestNotification.java | 2 -- .../event/handler/AbstractNotificationHandler.java | 12 +++++++++--- .../smbj/event/handler/NotificationHandler.java | 2 +- .../java/com/hierynomus/smbj/share/DiskShare.java | 4 +--- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/hierynomus/mssmb2/SMB2OplockLevel.java b/src/main/java/com/hierynomus/mssmb2/SMB2OplockLevel.java index 8349ab43..28887ebf 100644 --- a/src/main/java/com/hierynomus/mssmb2/SMB2OplockLevel.java +++ b/src/main/java/com/hierynomus/mssmb2/SMB2OplockLevel.java @@ -26,7 +26,7 @@ public enum SMB2OplockLevel implements EnumWithValue { SMB2_OPLOCK_LEVEL_II(0x01L), SMB2_OPLOCK_LEVEL_EXCLUSIVE(0x08L), SMB2_OPLOCK_LEVEL_BATCH(0x09L), - // TODO: implement and support using lease + // TODO implement and support using lease OPLOCK_LEVEL_LEASE(0xFFL); private long value; diff --git a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakFactory.java b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakFactory.java index 4279196d..87629a31 100644 --- a/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakFactory.java +++ b/src/main/java/com/hierynomus/mssmb2/messages/SMB2OplockBreakFactory.java @@ -33,7 +33,7 @@ public SMB2OplockBreak read(SMBBuffer buffer) throws Buffer.BufferException { buffer.rpos(0); final boolean isBreakNotification = messageId == breakMessageId; - // TODO: Use structureSize as well to determine oplock and lease. + // TODO Use structureSize as well to determine oplock and lease. // buffer.skip(64); // final int structureSize = buffer.readUInt16(); // buffer.rpos(0); diff --git a/src/main/java/com/hierynomus/smbj/event/AsyncCreateRequestNotification.java b/src/main/java/com/hierynomus/smbj/event/AsyncCreateRequestNotification.java index 0614bd31..f989ae6a 100644 --- a/src/main/java/com/hierynomus/smbj/event/AsyncCreateRequestNotification.java +++ b/src/main/java/com/hierynomus/smbj/event/AsyncCreateRequestNotification.java @@ -15,8 +15,6 @@ */ package com.hierynomus.smbj.event; -import com.hierynomus.smbj.common.SmbPath; - /*** * Event for notifying the messageId to DiskShare Notification Handler */ diff --git a/src/main/java/com/hierynomus/smbj/event/handler/AbstractNotificationHandler.java b/src/main/java/com/hierynomus/smbj/event/handler/AbstractNotificationHandler.java index 7172f11b..2d323c91 100644 --- a/src/main/java/com/hierynomus/smbj/event/handler/AbstractNotificationHandler.java +++ b/src/main/java/com/hierynomus/smbj/event/handler/AbstractNotificationHandler.java @@ -27,17 +27,23 @@ public abstract class AbstractNotificationHandler implements NotificationHandler @Override public void handleAsyncCreateRequestNotification( AsyncCreateRequestNotification asyncCreateRequestNotification) { - + // Empty method for override + // This is useful for user only implement some methods of NotificationHandler + // Leave this method Empty will perform nothing when received an asyncCreateRequestNotification } @Override public void handleAsyncCreateResponseNotification( AsyncCreateResponseNotification asyncCreateResponseNotification) { - + // Empty method for override + // This is useful for user only implement some methods of NotificationHandler + // Leave this method Empty will perform nothing when received an asyncCreateResponseNotification } @Override public void handleOplockBreakNotification(OplockBreakNotification oplockBreakNotification) { - + // Empty method for override + // This is useful for user only implement some methods of NotificationHandler + // Leave this method Empty will perform nothing when received an oplockBreakNotification } } diff --git a/src/main/java/com/hierynomus/smbj/event/handler/NotificationHandler.java b/src/main/java/com/hierynomus/smbj/event/handler/NotificationHandler.java index 7490576d..171d483c 100644 --- a/src/main/java/com/hierynomus/smbj/event/handler/NotificationHandler.java +++ b/src/main/java/com/hierynomus/smbj/event/handler/NotificationHandler.java @@ -20,7 +20,7 @@ import com.hierynomus.smbj.event.OplockBreakNotification; public interface NotificationHandler { - // TODO: add user reference property to allow user to check same NotificationHandler or not. + // TODO add user reference property to allow user to check same NotificationHandler or not. void handleAsyncCreateRequestNotification(AsyncCreateRequestNotification asyncCreateRequestNotification); void handleAsyncCreateResponseNotification(AsyncCreateResponseNotification asyncCreateResponseNotification); void handleOplockBreakNotification(OplockBreakNotification oplockBreakNotification); diff --git a/src/main/java/com/hierynomus/smbj/share/DiskShare.java b/src/main/java/com/hierynomus/smbj/share/DiskShare.java index 97d1c009..28378ce6 100644 --- a/src/main/java/com/hierynomus/smbj/share/DiskShare.java +++ b/src/main/java/com/hierynomus/smbj/share/DiskShare.java @@ -75,10 +75,9 @@ import static java.util.EnumSet.noneOf; public class DiskShare extends Share { - public static final EnumSet asyncSupport = EnumSet.of(SMB2_CREATE); + public static final EnumSet asyncSupport = of(SMB2_CREATE); private static final Logger logger = LoggerFactory.getLogger(DiskShare.class); private final PathResolver resolver; - private final SMBEventBus connectionPrivateBus; private NotificationHandler notificationHandler = null; private final boolean isCreatedTaskQueue; @@ -90,7 +89,6 @@ public class DiskShare extends Share { public DiskShare(SmbPath smbPath, TreeConnect treeConnect, PathResolver pathResolver, SMBEventBus connectionPrivateBus) { super(smbPath, treeConnect); this.resolver = pathResolver; - this.connectionPrivateBus = connectionPrivateBus; if (connectionPrivateBus != null) { connectionPrivateBus.subscribe(this); }