private async Task <object> SendMethodCall(MethodCache methodCache, object[] parameters) { if (methodCache == null) { throw new ArgumentException("The parameter cannot be null.", nameof(methodCache)); } if (methodCache == null) { throw new ArgumentException("The parameter cannot be null.", nameof(parameters)); } //PROTOCOL //CALL: //HEAD - 4 bytes - Identifier, ASCII (NTC1) //HEAD - integer - callback identifier //HEAD - uinteger - The method identifier //HEAD - integer * parameters - the length of each parameter //-------------------------------------------------------------------------- //DATA - length of the parameters - serialized parameters var callbackId = (uint)Interlocked.Increment(ref _callIdCounter); var buffer = new byte[CustomOffset /* user offset */ + 4 /* Header */ + 4 /* Callback id */ + 4 /* method id */ + parameters.Length * 4 /* parameter meta */ + EstimatedDataPerParameter * parameters.Length /* parameter data */]; var bufferOffset = CustomOffset + 12 + parameters.Length * 4; for (var i = 0; i < parameters.Length; i++) { var metaOffset = CustomOffset + 12 + i * 4; var parameterLength = _serializer.Serialize(methodCache.ParameterTypes[i], ref buffer, bufferOffset, parameters[i]); Buffer.BlockCopy(BitConverter.GetBytes(parameterLength), 0, buffer, metaOffset, 4); bufferOffset += parameterLength; } //write header buffer[CustomOffset] = CallProtocolInfo.Header1; buffer[CustomOffset + 1] = CallProtocolInfo.Header2; buffer[CustomOffset + 2] = CallProtocolInfo.Header3Call; buffer[CustomOffset + 3] = CallProtocolInfo.Header4; //write callback id Buffer.BlockCopy(BitConverter.GetBytes(callbackId), 0, buffer, CustomOffset + 4, 4); //method identifier Buffer.BlockCopy(BitConverter.GetBytes(methodCache.MethodId), 0, buffer, CustomOffset + 8, 4); var callback = new ResultCallback(); var callbackWait = callback.Wait(WaitTimeout); _callbacks.TryAdd(callbackId, callback); //impossible that this goes wrong OnSendData(new ArraySegment <byte>(buffer, 0, bufferOffset)).Forget(); //no need to await that using (callback) { if (!await callbackWait.ConfigureAwait(false)) { _callbacks.TryRemove(callbackId, out var _); throw new TimeoutException("The method call timed out, no response received."); } switch (callback.ResponseType) { case CallTransmissionResponseType.MethodExecuted: return(null); case CallTransmissionResponseType.ResultReturned: return(_serializer.Deserialize(methodCache.ReturnType, callback.Data, callback.Offset)); case CallTransmissionResponseType.Exception: var up = _serializer.DeserializeException(callback.Data, callback.Offset); throw up; case CallTransmissionResponseType.MethodNotImplemented: throw new NotImplementedException("The remote method is not implemented."); default: throw new ArgumentOutOfRangeException(); } } }
/// <summary> /// Called when data was received by the client side /// </summary> /// <param name="buffer">The array of unsigned bytes which contains the information to execute the method.</param> /// <param name="offset">The index into buffer at which the data begins</param> /// <returns>Returns the answer which should be sent back to the client</returns> public async Task <BufferSegment> ReceiveData(byte[] buffer, int offset) { //PROTOCOL //CALL: //HEAD - 4 bytes - Identifier, ASCII (NTC1) //HEAD - integer - callback identifier //HEAD - uinteger - The method identifier //HEAD - integer * parameters - the length of each parameter //-------------------------------------------------------------------------- //DATA - length of the parameters - serialized parameters // //RETURN: //HEAD - 4 bytes - Identifier, ASCII (NTR1) //HEAD - integer - callback identifier //HEAD - 1 byte - the response type (0 = executed, 1 = result returned, 2 = exception, 3 = not implemented) //(BODY - return object length - the serialized return object) if (buffer[offset++] != CallProtocolInfo.Header1 || buffer[offset++] != CallProtocolInfo.Header2 || buffer[offset++] != CallProtocolInfo.Header3Call) { throw new ArgumentException("Invalid package format. Invalid header."); } if (buffer[offset++] != 1) { throw new NotSupportedException($"The version {buffer[offset - 1]} is not supported."); } var id = BitConverter.ToUInt32(buffer, offset + 4); void WriteResponseHeader(byte[] data) { data[CustomOffset] = CallProtocolInfo.Header1; data[CustomOffset + 1] = CallProtocolInfo.Header2; data[CustomOffset + 2] = CallProtocolInfo.Header3Return; data[CustomOffset + 3] = CallProtocolInfo.Header4; Buffer.BlockCopy(buffer, offset, data, CustomOffset + 4, 4); //copy callback id } //method not found/implemented if (!Cache.MethodInvokers.TryGetValue(id, out var methodInvoker)) { var responseLength = CustomOffset /* user offset */ + 8 /* Header */ + 1 /* response type */; var response = Cache.BufferManager.TakeBuffer(responseLength); WriteResponseHeader(response); response[CustomOffset + 8] = (byte)CallTransmissionResponseType.MethodNotImplemented; return(new BufferSegment(response, 0, responseLength, Cache.BufferManager)); } var parameters = new object[methodInvoker.ParameterCount]; var parameterOffset = offset + 8 + parameters.Length * 4; for (var i = 0; i < methodInvoker.ParameterCount; i++) { var type = methodInvoker.ParameterTypes[i]; var parameterLength = BitConverter.ToInt32(buffer, offset + 8 + i * 4); parameters[i] = _serializer.Deserialize(type, buffer, parameterOffset); parameterOffset += parameterLength; } Task task; try { task = methodInvoker.Invoke(_interfaceImplementation, parameters); await task.ConfigureAwait(false); } catch (Exception e) { var responseLength = CustomOffset /* user offset */ + 8 /* Header */ + 1 /* response type */ + EstimatedResultBufferSize /* exception */; var takenBuffer = Cache.BufferManager.TakeBuffer(responseLength); var response = takenBuffer; var length = _serializer.SerializeException(ref response, CustomOffset + 9, e); WriteResponseHeader(response); response[CustomOffset + 8] = (byte)CallTransmissionResponseType.Exception; if (takenBuffer == response) { return(new BufferSegment(response, 0, length + 9 + CustomOffset, Cache.BufferManager)); } Cache.BufferManager.ReturnBuffer(takenBuffer); return(new BufferSegment(response, 0, length + 9 + CustomOffset)); } if (methodInvoker.ReturnsResult) { var result = methodInvoker.TaskReturnPropertyInfo.GetValue(task); var takenBuffer = Cache.BufferManager.TakeBuffer(CustomOffset + EstimatedResultBufferSize); var response = takenBuffer; WriteResponseHeader(response); response[CustomOffset + 8] = (byte)CallTransmissionResponseType.ResultReturned; var responseLength = _serializer.Serialize(methodInvoker.ReturnType, ref response, CustomOffset + 9, result); if (takenBuffer == response) { return(new BufferSegment(response, 0, responseLength + CustomOffset + 9, Cache.BufferManager)); } Cache.BufferManager.ReturnBuffer(takenBuffer); return(new BufferSegment(response, 0, responseLength + CustomOffset + 9)); } else { var responseLength = CustomOffset /* user offset */ + 8 /* Header */ + 1 /* response type */; var response = Cache.BufferManager.TakeBuffer(responseLength); WriteResponseHeader(response); response[CustomOffset + 8] = (byte)CallTransmissionResponseType.MethodExecuted; return(new BufferSegment(response, 0, responseLength, Cache.BufferManager)); } }