2
0
mirror of https://github.com/esiur/esiur-js.git synced 2025-05-06 04:22:58 +00:00
esiur-js/src/Net/IIP/DistributedConnection.js
2024-06-24 19:45:19 +03:00

3600 lines
128 KiB
JavaScript

/*
* Copyright (c) 2017-2022 Ahmed Kh. Zamil
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Created by Ahmed Zamil on 25/07/2017.
*/
"use strict";
import IStore from '../../Resource/IStore.js';
import Session from '../../Security/Authority/Session.js';
import Authentication from '../../Security/Authority/Authentication.js';
import AuthenticationType from "../../Security/Authority/AuthenticationType.js";
import SHA256 from '../../Security/Integrity/SHA256.js';
import { BL, DC } from '../../Data/DC.js';
import SendList from '../SendList.js';
import AsyncReply from '../../Core/AsyncReply.js';
import Codec from '../../Data/Codec.js';
import KeyList from '../../Data/KeyList.js';
import AsyncQueue from '../../Core/AsyncQueue.js';
import Warehouse from '../../Resource/Warehouse.js';
import IIPAuthPacket from "../Packets/IIPAuthPacket.js";
import IIPAuthPacketCommand from "../Packets/IIPAuthPacketCommand.js";
import IIPAuthPacketHeader from '../../Net/Packets/IIPAuthPacketHeader.js';
import IIPAuthPacketInitialize from '../../Net/Packets/IIPAuthPacketInitialize.js';
import IIPAuthPacketAcknowledge from '../../Net/Packets/IIPAuthPacketAcknowledge.js';
import IIPAuthPacketAction from '../../Net/Packets/IIPAuthPacketAction.js';
import IIPAuthPacketEvent from '../../Net/Packets/IIPAuthPacketEvent.js';
import AuthenticationMethod from "../../Security/Authority/AuthenticationMethod.js";
import IIPPacket from "../Packets/IIPPacket.js";
import IIPPacketAction from "../Packets/IIPPacketAction.js";
import IIPPacketCommand from "../Packets/IIPPacketCommand.js";
import IIPPacketEvent from "../Packets/IIPPacketEvent.js";
import IIPPacketReport from "../Packets//IIPPacketReport.js";
import ErrorType from "../../Core/ErrorType.js";
import ProgressType from "../../Core/ProgressType.js";
import ExceptionCode from "../../Core/ExceptionCode.js";
import DistributedResource from './DistributedResource.js';
import TypeTemplate from '../../Resource/Template/TypeTemplate.js';
import DistributedResourceQueueItem from './DistributedResourceQueueItem.js';
import DistributedResourceQueueItemType from './DistributedResourceQueueItemType.js';
import DistributedPropertyContext from './DistributedPropertyContext.js';
import { ResourceTrigger } from '../../Resource/IResource.js';
import Ruling from '../../Security/Permissions/Ruling.js';
import ActionType from '../../Security/Permissions/ActionType.js';
import AsyncException from '../../Core/AsyncException.js';
import WSocket from '../Sockets/WSocket.js';
import ClientAuthentication from "../../Security/Authority/ClientAuthentication.js";
import HostAuthentication from "../../Security/Authority/HostAuthentication.js";
import SocketState from "../Sockets/SocketState.js";
import TemplateType from '../../Resource/Template/TemplateType.js';
import AsyncBag from '../../Core/AsyncBag.js';
import {TransmissionType, TransmissionTypeIdentifier} from '../../Data/TransmissionType.js';
import PropertyValue from '../../Data/PropertyValue.js';
import PropertyValueArray from '../../Data/PropertyValueArray.js';
import { UInt8 } from '../../Data/ExtendedTypes.js';
import ConnectionStatus from './ConnectionStatus.js';
import { Prop, TemplateDescriber } from '../../Resource/Template/TemplateDescriber.js';
import TypedMap from '../../Data/TypedMap.js';
import Global from '../../Misc/Global.js';
import IIPAuthPacketHashAlgorithm from '../../Net/Packets/IIPAuthPacketHashAlgorithm.js';
import AuthorizationResultsResponse from '../../Security/Membership/AuthorizationResultsResponse.js';
import IIPAuthPacketIAuthHeader from '../../Net/Packets/IIPAuthPacketIAuthHeader.js';
import AuthorizationRequest from '../../Security/Membership/AuthorizationRequest.js';
export default class DistributedConnection extends IStore {
// fields
#port;
#hostname;
#secure;
#socket;
#lastKeepAliveSent;
#lastKeepAliveReceived;
#status;
#readyToEstablish = false;
#openReply;
#session = new Session();
#packet = new IIPPacket();
#authPacket = new IIPAuthPacket();
#neededResources = new KeyList();
#attachedResources = new KeyList();
#suspendedResources = new KeyList();
#invalidCredentials = false;
#localPasswordOrToken = null;
#keepAliveTimer;
#loginDate;
#jitter = 0;
keepAliveTime = 10;
keepAliveInterval = 30;
reconnectInterval = 5;
autoReconnect = false;
#server;
#templates = new KeyList();
#requests = new KeyList();// {};
#templateRequests = new KeyList();
#templateByNameRequests = new KeyList();
#resourceRequests = new KeyList();// {};
#callbackCounter = 0;
#queue = new AsyncQueue();
#subscriptions = new Map();
#ready;
get jitter() {
return this.#jitter;
}
get session() {
return this.#session;
}
get status () {
return this.#status;
}
_sendAll(data) {
this.#socket.sendAll(data.buffer);
}
#sendParams(doneReply) {
return new SendList(this, doneReply);
}
constructor(server) {
super();
this.#session.authenticationType = AuthenticationType.Host;
this.#session.localMethod = AuthenticationMethod.None;
this.#session.localHeaders.set(IIPAuthPacketHeader.Nonce, Global.generateBytes(32));
this.#server = server;
this._register("ready");
this._register("error");
this._register("close");
this._register("resumed");
this.#queue.then(function (x) {
if (x.type == DistributedResourceQueueItemType.Event) {
x.resource._emitEventByIndex(x.index, x.value);
}
else {
x.resource._updatePropertyByIndex(x.index, x.value);
}
});
// set local nonce
}
#processPacket(msg, offset, ends, data) {
if (this.#ready) {
let packet = this.#packet;
var rt = packet.parse(msg, offset, ends);
//console.log("Inc " , rt, offset, ends);
if (rt <= 0) {
data.holdFor(msg, offset, ends - offset, -rt);
return ends;
}
else {
offset += rt;
try {
if (packet.command == IIPPacketCommand.Event) {
switch (packet.event) {
case IIPPacketEvent.ResourceReassigned:
this.IIPEventResourceReassigned(packet.resourceId, packet.newResourceId);
break;
case IIPPacketEvent.ResourceDestroyed:
this.IIPEventResourceDestroyed(packet.resourceId);
break;
case IIPPacketEvent.PropertyUpdated:
this.IIPEventPropertyUpdated(packet.resourceId, packet.methodIndex, packet.dataType, msg);
break;
case IIPPacketEvent.EventOccurred:
this.IIPEventEventOccurred(packet.resourceId, packet.methodIndex, packet.dataType, msg);
break;
case IIPPacketEvent.ChildAdded:
this.IIPEventChildAdded(packet.resourceId, packet.childId);
break;
case IIPPacketEvent.ChildRemoved:
this.IIPEventChildRemoved(packet.resourceId, packet.childId);
break;
case IIPPacketEvent.Renamed:
this.IIPEventRenamed(packet.resourceId, packet.resourceName);
break;
case IIPPacketEvent.AttributesUpdated:
//@TODO: fix this
//this.IIPEventAttributesUpdated(packet.resourceId, packet.content);
break;
}
}
else if (packet.command == IIPPacketCommand.Request) {
switch (packet.action) {
// Manage
case IIPPacketAction.AttachResource:
this.IIPRequestAttachResource(packet.callbackId, packet.resourceId);
break;
case IIPPacketAction.ReattachResource:
this.IIPRequestReattachResource(packet.callbackId, packet.resourceId, packet.resourceAge);
break;
case IIPPacketAction.DetachResource:
this.IIPRequestDetachResource(packet.callbackId, packet.resourceId);
break;
case IIPPacketAction.CreateResource:
// @TODO: implement this
// this.IIPRequestCreateResource(packet.callbackId, packet.storeId, packet.resourceId, packet.content);
break;
case IIPPacketAction.DeleteResource:
this.IIPRequestDeleteResource(packet.callbackId, packet.resourceId);
break;
case IIPPacketAction.AddChild:
this.IIPRequestAddChild(packet.callbackId, packet.resourceId, packet.childId);
break;
case IIPPacketAction.RemoveChild:
this.IIPRequestRemoveChild(packet.callbackId, packet.resourceId, packet.childId);
break;
case IIPPacketAction.RenameResource:
this.IIPRequestRenameResource(packet.callbackId, packet.resourceId, packet.resourceName);
break;
// Inquire
case IIPPacketAction.TemplateFromClassName:
this.IIPRequestTemplateFromClassName(packet.callbackId, packet.className);
break;
case IIPPacketAction.TemplateFromClassId:
this.IIPRequestTemplateFromClassId(packet.callbackId, packet.classId);
break;
case IIPPacketAction.TemplateFromResourceId:
this.IIPRequestTemplateFromResourceId(packet.callbackId, packet.resourceId);
break;
case IIPPacketAction.QueryLink:
this.IIPRequestQueryResources(packet.callbackId, packet.resourceLink);
break;
case IIPPacketAction.ResourceChildren:
this.IIPRequestResourceChildren(packet.callbackId, packet.resourceId);
break;
case IIPPacketAction.ResourceParents:
this.IIPRequestResourceParents(packet.callbackId, packet.resourceId);
break;
case IIPPacketAction.ResourceHistory:
this.IIPRequestInquireResourceHistory(packet.callbackId, packet.resourceId, packet.fromDate, packet.toDate);
break;
case IIPPacketAction.LinkTemplates:
this.IIPRequestLinkTemplates(packet.callbackId, packet.resourceLink);
break;
// Invoke
case IIPPacketAction.InvokeFunction:
this.IIPRequestInvokeFunction(packet.callbackId, packet.resourceId, packet.methodIndex, packet.dataType, msg);
break;
// case IIPPacketAction.GetProperty:
// this.IIPRequestGetProperty(packet.callbackId, packet.resourceId, packet.methodIndex);
// break;
// case IIPPacketAction.GetPropertyIfModified:
// this.IIPRequestGetPropertyIfModifiedSince(packet.callbackId, packet.resourceId, packet.methodIndex, packet.resourceAge);
// break;
case IIPPacketAction.Listen:
this.IIPRequestListen(packet.callbackId, packet.resourceId, packet.methodIndex);
break;
case IIPPacketAction.Unlisten:
this.IIPRequestUnlisten(packet.callbackId, packet.resourceId, packet.methodIndex);
break;
case IIPPacketAction.SetProperty:
this.IIPRequestSetProperty(packet.callbackId, packet.resourceId, packet.methodIndex, packet.dataType, msg);
break;
// Attribute @TODO: implement these
case IIPPacketAction.GetAllAttributes:
// this.IIPRequestGetAttributes(packet.callbackId, packet.resourceId, packet.content, true);
break;
case IIPPacketAction.UpdateAllAttributes:
// this.IIPRequestUpdateAttributes(packet.callbackId, packet.resourceId, packet.content, true);
break;
case IIPPacketAction.ClearAllAttributes:
// this.IIPRequestClearAttributes(packet.callbackId, packet.resourceId, packet.content, true);
break;
case IIPPacketAction.GetAttributes:
// this.IIPRequestGetAttributes(packet.callbackId, packet.resourceId, packet.content, false);
break;
case IIPPacketAction.UpdateAttributes:
// this.IIPRequestUpdateAttributes(packet.callbackId, packet.resourceId, packet.content, false);
break;
case IIPPacketAction.ClearAttributes:
// this.IIPRequestClearAttributes(packet.callbackId, packet.resourceId, packet.content, false);
break;
case IIPPacketAction.KeepAlive:
this.IIPRequestKeepAlive(packet.callbackId, packet.currentTime, packet.interval);
break;
case IIPPacketAction.ProcedureCall:
this.IIPRequestProcedureCall(packet.callbackId, packet.procedure, packet.dataType, msg);
break;
case IIPPacketAction.StaticCall:
this.IIPRequestStaticCall(packet.callbackId, packet.classId, packet.methodIndex, packet.dataType, msg);
break;
}
}
else if (packet.command == IIPPacketCommand.Reply) {
switch (packet.action) {
case IIPPacketAction.AttachResource:
this.IIPReply(packet.callbackId, packet.classId, packet.resourceAge, packet.resourceLink, packet.dataType, msg);
break;
case IIPPacketAction.ReattachResource:
this.IIPReply(packet.callbackId, packet.resourceAge, packet.dataType, msg);
break;
case IIPPacketAction.DetachResource:
this.IIPReply(packet.callbackId);
break;
case IIPPacketAction.CreateResource:
this.IIPReply(packet.callbackId, packet.resourceId);
break;
case IIPPacketAction.DeleteResource:
case IIPPacketAction.AddChild:
case IIPPacketAction.RemoveChild:
case IIPPacketAction.RenameResource:
this.IIPReply(packet.callbackId);
break;
case IIPPacketAction.TemplateFromClassName:
case IIPPacketAction.TemplateFromClassId:
case IIPPacketAction.TemplateFromResourceId:
if (packet.dataType != null) {
var content = msg.clip(packet.dataType?.offset ?? 0,
packet.dataType?.contentLength ?? 0);
this.IIPReply(packet.callbackId, TypeTemplate.parse(content));
} else {
iipReportError(packet.callbackId, ErrorType.Management,
ExceptionCode.TemplateNotFound.index, "Template not found");
}
break;
case IIPPacketAction.QueryLink:
case IIPPacketAction.ResourceChildren:
case IIPPacketAction.ResourceParents:
case IIPPacketAction.ResourceHistory:
case IIPPacketAction.LinkTemplates:
this.IIPReply(packet.callbackId, packet.dataType, msg);
break;
case IIPPacketAction.InvokeFunction:
case IIPPacketAction.StaticCall:
case IIPPacketAction.ProcedureCall:
this.IIPReplyInvoke(packet.callbackId, packet.dataType, msg);
break;
// case IIPPacketAction.GetProperty:
// this.IIPReply(packet.callbackId, packet.content);
// break;
// case IIPPacketAction.GetPropertyIfModified:
// this.IIPReply(packet.callbackId, packet.content);
// break;
case IIPPacketAction.Listen:
case IIPPacketAction.Unlisten:
case IIPPacketAction.SetProperty:
this.IIPReply(packet.callbackId);
break;
// Attribute
case IIPPacketAction.GetAllAttributes:
case IIPPacketAction.GetAttributes:
this.IIPReply(packet.callbackId, packet.dataType, msg);
break;
case IIPPacketAction.UpdateAllAttributes:
case IIPPacketAction.UpdateAttributes:
case IIPPacketAction.ClearAllAttributes:
case IIPPacketAction.ClearAttributes:
this.IIPReply(packet.callbackId);
break;
case IIPPacketAction.KeepAlive:
this.IIPReply(packet.callbackId, packet.currentTime, packet.jitter);
break;
}
}
else if (packet.command == IIPPacketCommand.Report) {
switch (packet.report) {
case IIPPacketReport.ManagementError:
this.IIPReportError(packet.callbackId, ErrorType.Management, packet.errorCode, null);
break;
case IIPPacketReport.ExecutionError:
this.IIPReportError(packet.callbackId, ErrorType.Exception, packet.errorCode, packet.errorMessage);
break;
case IIPPacketReport.ProgressReport:
this.IIPReportProgress(packet.callbackId, ProgressType.Execution, packet.progressValue, packet.progressMax);
break;
case IIPPacketReport.ChunkStream:
this.IIPReportChunk(packet.callbackId, packet.dataType, msg);
break;
}
}
} catch (ex) {
console.log("Esiur Error ", ex);
}
}
}
else {
let authPacket = this.#authPacket;
let rt = authPacket.parse(msg, offset, ends);
if (rt <= 0) {
data.holdAllFor(msg, ends - rt);
return ends;
}
else {
offset += rt;
if (this.#session.authenticationType == AuthenticationType.Host) {
this.#processHostAuth(msg);
}
else if (this.#session.authenticationType == AuthenticationType.Client) {
this.#processClientAuth(msg);
}
}
}
return offset;
}
#processClientAuth(data)
{
let authPacket = this.#authPacket;
let session = this.#session;
if (authPacket.command == IIPAuthPacketCommand.Acknowledge)
{
// if there is a mismatch in authentication
if (session.localMethod != authPacket.remoteMethod
|| session.remoteMethod != authPacket.localMethod)
{
this.#openReply?.triggerError(new Exception("Peer refused authentication method."));
this.#openReply = null;
}
// Parse remote headers
var dataType = authPacket.dataType;
var pr = Codec.parse(data, dataType.offset, this, null, dataType);
var rt= pr.reply.result;
session.remoteHeaders = rt;
if (session.localMethod == AuthenticationMethod.None)
{
// send establish
this.#sendParams()
.addUint8(IIPAuthPacketAction.EstablishNewSession)
.done();
}
else if (session.localMethod == AuthenticationMethod.Credentials
|| session.localMethod == AuthenticationMethod.Token)
{
var remoteNonce = session.remoteHeaders.get(IIPAuthPacketHeader.Nonce);
var localNonce = session.localHeaders.get(IIPAuthPacketHeader.Nonce);
// send our hash
// local nonce + password or token + remote nonce
var challenge = SHA256.compute((BL()
.addDC(localNonce)
.addDC(this.#localPasswordOrToken)
.addDC(remoteNonce))
.toDC());
this.#sendParams()
.addUint8(IIPAuthPacketAction.AuthenticateHash)
.addUint8(IIPAuthPacketHashAlgorithm.SHA256)
.addUint16(challenge.length)
.addDC(challenge)
.done();
}
}
else if (authPacket.command == IIPAuthPacketCommand.Action)
{
if (authPacket.action == IIPAuthPacketAction.AuthenticateHash)
{
var remoteNonce = session.remoteHeaders.get(IIPAuthPacketHeader.Nonce);
var localNonce = session.localHeaders.get(IIPAuthPacketHeader.Nonce);
// check if the server knows my password
var challenge = SHA256.compute((BL()
.addDC(remoteNonce)
.addDC(this.#localPasswordOrToken)
.addDC(localNonce)
).toDC());
if (challenge.sequenceEqual(authPacket.challenge))
{
// send establish request
this.#sendParams()
.addUint8(IIPAuthPacketAction.EstablishNewSession)
.done();
}
else
{
this.#sendParams()
.addUint8(IIPAuthPacketEvent.ErrorTerminate)
.addUint8(ExceptionCode.ChallengeFailed.index)
.addUint16(16)
.addString("Challenge Failed")
.done();
}
}
}
else if (authPacket.command == IIPAuthPacketCommand.Event)
{
if (authPacket.event == IIPAuthPacketEvent.ErrorTerminate
|| authPacket.event == IIPAuthPacketEvent.ErrorMustEncrypt
|| authPacket.event == IIPAuthPacketEvent.ErrorRetry)
{
this.#invalidCredentials = true;
this.#openReply?.triggerError(new AsyncException(ErrorType.Management, authPacket.errorCode, authPacket.message));
this.#openReply = null;
var ex = new AsyncException(ErrorType.Management, authPacket.errorCode,
authPacket.message);
this._emit("error", this, ex);
this.close();
}
else if (authPacket.event == IIPAuthPacketEvent.IndicationEstablished)
{
session.id = authPacket.sessionId;
session.authorizedAccount = authPacket.accountId.getString(0, authPacket.accountId.length);
this.#ready = true;
this.#status = ConnectionStatus.Connected;
// put it in the warehouse
if (this.instance == null)
{
Warehouse.put(session.authorizedAccount.replaceAll("/", "_"), this, null, this.#server).then((x) =>
{
this.#openReply?.trigger(true);
this._emit("ready", this);
this.#openReply = null;
}).error((x) =>
{
this.#openReply?.triggerError(x);
this.#openReply = null;
});
}
else
{
this.#openReply?.trigger(true);
this.#openReply = null;
this._emit("ready", this);
}
// start perodic keep alive timer
this.#keepAliveTimer = setInterval(this.#keepAliveTimerElapsed.bind(this), this.keepAliveInterval * 1000);
}
else if (authPacket.event == IIPAuthPacketEvent.IAuthPlain)
{
let dataType = authPacket.dataType;
let pr = Codec.parse(data, dataType.offset, this, null, dataType);
let headers = pr.reply.result;
let iAuthRequest = new AuthorizationRequest(headers);
if (authenticator == null)
{
this.#sendParams()
.addUint8(IIPAuthPacketEvent.ErrorTerminate)
.addUint8(ExceptionCode.NotSupported.index)
.addUint16(13)
.addString("Not supported")
.done();
}
else
{
this.authenticator(iAuthRequest).then((response) =>
{
this.#sendParams()
.addUint8(IIPAuthPacketAction.IAuthPlain)
.addUint32(headers.get(IIPAuthPacketIAuthHeader.Reference))
.addDC(Codec.compose(response, this))
.done();
})
.timeout(iAuthRequest.timeout * 1000,
() => {
this.#sendParams()
.addUint8(IIPAuthPacketEvent.ErrorTerminate)
.addUint8(ExceptionCode.Timeout.index)
.addUint16(7)
.addString("Timeout")
.done();
}
);
}
}
else if (authPacket.event == IIPAuthPacketEvent.IAuthHashed)
{
let dataType = authPacket.dataType;
let parsed = Codec.parse(data, dataType.offset, this, null, dataType);
let headers = parsed.reply.result;
let iAuthRequest = new AuthorizationRequest(headers);
if (this.authenticator == null)
{
this.#sendParams()
.addUint8(IIPAuthPacketEvent.ErrorTerminate)
.addUint8(ExceptionCode.NotSupported.index)
.addUint16(13)
.addString("Not supported")
.done();
}
else
{
this.authenticator(iAuthRequest).then((response) =>
{
var hash = SHA256.compute((BL()
.addDC(session.localHeaders.get(IIPAuthPacketHeader.Nonce))
.addDC(Codec.compose(response, this))
.addDC(session.remoteHeaders.get(IIPAuthPacketHeader.Nonce))
).toDC());
this.#sendParams()
.addUint8(IIPAuthPacketAction.IAuthHashed)
.addUint32(headers.get(IIPAuthPacketIAuthHeader.Reference))
.addUint8(IIPAuthPacketHashAlgorithm.SHA256)
.addUint16(hash.length)
.addDC(hash)
.done();
})
.timeout(iAuthRequest.timeout * 1000,
() => {
this.#sendParams()
.addUint8(IIPAuthPacketEvent.ErrorTerminate)
.addUint8(ExceptionCode.Timeout.index)
.addUint16(7)
.addString("Timeout")
.done();
});
}
}
else if (authPacket.event == IIPAuthPacketEvent.IAuthEncrypted)
{
throw new Exception("IAuthEncrypted not implemented.");
}
}
}
#processHostAuth(data)
{
let authPacket = this.#authPacket;
let session = this.#session;
if (authPacket.command == IIPAuthPacketCommand.Initialize)
{
// Parse headers
var dataType = authPacket.dataType;
var parsed = Codec.parse(data, dataType.offset, this, null, dataType);
let rt = parsed.reply.result;
session.remoteHeaders = rt;
session.remoteMethod = authPacket.localMethod;
if (authPacket.initialization == IIPAuthPacketInitialize.CredentialsNoAuth)
{
try
{
var username = session.remoteHeaders.get(IIPAuthPacketHeader.Username);
var domain = session.remoteHeaders.get(IIPAuthPacketHeader.Domain);
if (this.#server?.membership == null)
{
var errMsg = DC.stringToBytes("Membership not set.");
this.#sendParams()
.addUint8(IIPAuthPacketEvent.ErrorTerminate)
.addUint8(ExceptionCode.GeneralFailure.index)
.addUint16(errMsg.length)
.addDC(errMsg)
.done();
}
else {
this.#server.membership.userExists(username, domain).then((x) =>
{
if (x != null)
{
session.authorizedAccount = x;
var localHeaders = session.localHeaders;
this.#sendParams()
.addUint8(IIPAuthPacketAcknowledge.NoAuthCredentials)
.addDC(Codec.compose(localHeaders, this))
.done();
}
else
{
// Send user not found error
this.#sendParams()
.addUint8(IIPAuthPacketEvent.ErrorTerminate)
.addUint8(ExceptionCode.UserOrTokenNotFound.index)
.addUint16(14)
.addString("User not found")
.done();
}
});
}
}
catch (ex)
{
// Send the server side error
let errMsg = DC.stringToBytes(ex.toString());
this.#sendParams()
.addUint8(IIPAuthPacketEvent.ErrorTerminate)
.addUint8(ExceptionCode.GeneralFailure.index)
.addUint16(errMsg.length)
.addDC(errMsg)
.done();
}
}
else if (authPacket.initialization == IIPAuthPacketInitialize.TokenNoAuth)
{
try
{
if (this.#server?.membership == null)
{
this.#sendParams()
.addUint8(IIPAuthPacketEvent.ErrorTerminate)
.addUint8(ExceptionCode.UserOrTokenNotFound.index)
.addUint16(15)
.addString("Token not found")
.done();
}
// Check if user and token exists
else
{
let tokenIndex = session.remoteHeaders.get(IIPAuthPacketHeader.TokenIndex);
let domain = session.remoteHeaders.get(IIPAuthPacketHeader.Domain);
this.#server?.membership?.tokenExists(tokenIndex, domain).then((x) =>
{
if (x != null)
{
session.authorizedAccount = x;
let localHeaders = session.localHeaders;
this.#sendParams()
.addUint8(IIPAuthPacketAcknowledge.NoAuthToken)
.addDC(Codec.compose(localHeaders, this))
.done();
}
else
{
// Send token not found error.
this.#sendParams()
.addUint8(IIPAuthPacketEvent.ErrorTerminate)
.addUint8(ExceptionCode.UserOrTokenNotFound.index)
.addUint16(15)
.addString("Token not found")
.done();
}
});
}
}
catch (ex)
{
// Sender server side error.
let errMsg = DC.stringToBytes(ex.toString());
this.#sendParams()
.addUint8(IIPAuthPacketEvent.ErrorTerminate)
.addUint8(ExceptionCode.GeneralFailure.index)
.addUint16(errMsg.length)
.addDC(errMsg)
.done();
}
}
else if (authPacket.initialization == IIPAuthPacketInitialize.NoAuthNoAuth)
{
try
{
// Check if guests are allowed
if (this.#server?.membership?.guestsAllowed ?? true)
{
let localHeaders = session.localHeaders;
session.authorizedAccount = "g-" + Global.generateCode();
this.#readyToEstablish = true;
this.#sendParams()
.addUint8(IIPAuthPacketAcknowledge.NoAuthNoAuth)
.addDC(Codec.compose(localHeaders, this))
.done();
}
else
{
// Send access denied error because the server does not allow guests.
this.#sendParams()
.addUint8(IIPAuthPacketEvent.ErrorTerminate)
.addUint8(ExceptionCode.AccessDenied.index)
.addUint16(18)
.addString("Guests not allowed")
.done();
}
}
catch (ex)
{
// Send the server side error.
let errMsg = DC.stringToBytes(ex.toString());
this.#sendParams()
.addUint8(IIPAuthPacketEvent.ErrorTerminate)
.addUint8(ExceptionCode.GeneralFailure.index)
.addUint16(errMsg.length)
.addDC(errMsg)
.done();
}
}
}
else if (authPacket.command == IIPAuthPacketCommand.Action)
{
if (authPacket.action == IIPAuthPacketAction.AuthenticateHash)
{
let remoteHash = authPacket.challenge;
let reply;
try
{
if (session.remoteMethod == AuthenticationMethod.Credentials)
{
reply = this.#server.membership.getPassword(session.remoteHeaders.get(IIPAuthPacketHeader.Username),
session.remoteHeaders.get(IIPAuthPacketHeader.Domain));
}
else if (session.remoteMethod == AuthenticationMethod.Token)
{
reply = this.#server.membership.getToken(session.remoteHeaders.get(IIPAuthPacketHeader.TokenIndex),
session.remoteHeaders.get(IIPAuthPacketHeader.Domain));
}
else
{
// Error
throw Exception("Unsupported authentication method");
}
reply.then((pw) =>
{
if (pw != null)
{
let localNonce = session.localHeaders.get(IIPAuthPacketHeader.Nonce);
let remoteNonce = session.remoteHeaders.get(IIPAuthPacketHeader.Nonce);
let hash = SHA256.compute((BL()
.addDC(remoteNonce)
.addDC(pw)
.addDC(localNonce)
).toDC());
if (hash.sequenceEqual(remoteHash))
{
// send our hash
let localHash = SHA256.compute((BL()
.addDC(localNonce)
.addDC(pw)
.addDC(remoteNonce)
).toDC());
this.#sendParams()
.addUint8(IIPAuthPacketAction.AuthenticateHash)
.addUint8(IIPAuthPacketHashAlgorithm.SHA256)
.addUint16(localHash.length)
.addDC(localHash)
.done();
this.#readyToEstablish = true;
}
else
{
this.#sendParams()
.addUint8(IIPAuthPacketEvent.ErrorTerminate)
.addUint8(ExceptionCode.AccessDenied.index)
.addUint16(13)
.addString("Access Denied")
.done();
}
}
});
}
catch (ex)
{
var errMsg = DC.stringToBytes(ex.toString());
this.#sendParams()
.addUint8(IIPAuthPacketEvent.ErrorTerminate)
.addUint8(ExceptionCode.GeneralFailure.index)
.addUint16(errMsg.length)
.addDC(errMsg)
.done();
}
}
else if (authPacket.action == IIPAuthPacketAction.IAuthPlain)
{
var reference = authPacket.reference;
var dataType = authPacket.dataType;
var parsed = Codec.parse(data, dataType.offset, this, null, dataType);
var value = parsed.reply.result;
this.#server?.membership?.authorizePlain(session, reference, value)
.then((x) => this.#processAuthorization(x));
}
else if (authPacket.action == IIPAuthPacketAction.IAuthHashed)
{
let reference = authPacket.reference;
let value = authPacket.challenge;
let algorithm = authPacket.hashAlgorithm;
let self = this;
this.#server?.membership?.authorizeHashed(session, reference, algorithm, value)
.then((x) => self.#processAuthorization(x));
}
else if (authPacket.action == IIPAuthPacketAction.IAuthEncrypted)
{
let reference = authPacket.reference;
let value = authPacket.challenge;
let algorithm = authPacket.publicKeyAlgorithm;
let self = this;
this.#server?.membership?.authorizeEncrypted(session, reference, algorithm, value)
.then((x) => self.#processAuthorization(x));
}
else if (authPacket.action == IIPAuthPacketAction.EstablishNewSession)
{
if (this.#readyToEstablish)
{
if (this.#server?.membership == null)
{
this.#processAuthorization(null);
}
else
{
let self = this;
this.#server?.membership?.authorize(session).then((x) =>
{
self.#processAuthorization(x);
});
}
}
else
{
this.#sendParams()
.addUint8(IIPAuthPacketEvent.ErrorTerminate)
.addUint8(ExceptionCode.GeneralFailure.index)
.addUint16(9)
.addString("Not ready")
.done();
}
}
}
}
#processAuthorization(results)
{
if (results == null || results.response == AuthorizationResultsResponse.Success)
{
this.#session.id = Global.generateCode(32);
let accountId = DC.stringToBytes(this.#session.authorizedAccount);
this.#sendParams()
.addUint8(IIPAuthPacketEvent.IndicationEstablished)
.addUint8(this.#session.id.length)
.addUint8Array(this.#session.id)
.addUint8(accountId.length)
.addUint8Array(accountId)
.done();
if (this.instance == null)
{
Warehouse.put(this.#session.authorizedAccount.replaceAll("/", "_"), this, null, this.#server).then((x) =>
{
this.#ready = true;
this.#status = ConnectionStatus.Connected;
this.#openReply?.trigger(true);
this.#openReply = null;
this._emit("ready", this);
this.#server?.membership?.login(this.#session);
this.#loginDate = new Date();
}).error((x) =>
{
this.#openReply?.triggerError(x);
this.#openReply = null;
});
}
else
{
this.#ready = true;
this.#status = ConnectionStatus.Connected;
this.#openReply?.trigger(true);
this.#openReply = null;
this._emit("ready", this);
this.#server?.membership?.login(session);
}
}
else if (results.response == AuthorizationResultsResponse.Failed)
{
this.#sendParams()
.addUint8(IIPAuthPacketEvent.ErrorTerminate)
.addUint8(ExceptionCode.ChallengeFailed.index)
.addUint16(21)
.addString("Authentication failed")
.done();
}
else if (results.response == AuthorizationResultsResponse.Expired)
{
this.#sendParams()
.addUint8(IIPAuthPacketEvent.ErrorTerminate)
.addUint8(ExceptionCode.Timeout.index)
.addUint16(22)
.addString("Authentication expired")
.done();
}
else if (results.response == AuthorizationResultsResponse.ServiceUnavailable)
{
this.#sendParams()
.addUint8(IIPAuthPacketEvent.ErrorTerminate)
.addUint8(ExceptionCode.GeneralFailure.index)
.addUint16(19)
.addString("Service unavailable")
.done();
}
else if (results.response == AuthorizationResultsResponse.IAuthPlain)
{
var args = new (TypedMap.of(UInt8, Object))();
args.set(IIPAuthPacketIAuthHeader.Reference, results.reference);
args.set(IIPAuthPacketIAuthHeader.Destination, results.destination);
args.set(IIPAuthPacketIAuthHeader.Expire, results.expire);
args.set(IIPAuthPacketIAuthHeader.Clue, results.clue);
args.set(IIPAuthPacketIAuthHeader.RequiredFormat, results.requiredFormat);
this.#sendParams()
.addUint8(IIPAuthPacketEvent.IAuthPlain)
.addDC(Codec.compose(args, this))
.done();
}
else if (results.response == AuthorizationResultsResponse.IAuthHashed)
{
var args = new (TypedMap.of(UInt8, Object))();
args.set(IIPAuthPacketIAuthHeader.Reference, results.reference);
args.set(IIPAuthPacketIAuthHeader.Destination, results.destination);
args.set(IIPAuthPacketIAuthHeader.Expire, results.expire);
args.set(IIPAuthPacketIAuthHeader.Clue, results.clue);
args.set(IIPAuthPacketIAuthHeader.RequiredFormat, results.requiredFormat);
this.#sendParams()
.addUint8(IIPAuthPacketEvent.IAuthHashed)
.addDC(Codec.compose(args, this))
.done();
}
else if (results.response == AuthorizationResultsResponse.IAuthEncrypted)
{
var args = new (TypedMap.of(UInt8, Object))();
args.set(IIPAuthPacketIAuthHeader.Reference, results.reference);
args.set(IIPAuthPacketIAuthHeader.Destination, results.destination);
args.set(IIPAuthPacketIAuthHeader.Expire, results.expire);
args.set(IIPAuthPacketIAuthHeader.Clue, results.clue);
args.set(IIPAuthPacketIAuthHeader.RequiredFormat, results.requiredFormat);
this.#sendParams()
.addUint8(IIPAuthPacketEvent.IAuthEncrypted)
.addDC(Codec.compose(args, this))
.done();
}
}
#dataReceived(data)
{
var msg = data.read();
let offset = 0;
let ends = msg.length;
this.#socket.hold();
try
{
while (offset < ends)
{
offset = this.#processPacket(msg, offset, ends, data);
}
}
catch (ex)
{
console.log(ex);
}
this.#socket?.unhold();
}
close(event) {
try {
this.#socket.close();
}
catch {
}
}
async reconnect() {
console.log("Reconnecting...");
try {
if (!await this.connect())
return false;
try {
let toBeRestored = [];
for(var i = 0 ; i < this.#suspendedResources.length; i++)
{
var r = this.#suspendedResources.values[i].deref();
if (r != null)
toBeRestored.push(r);
}
for(let r of toBeRestored)
{
let link = DC.stringToBytes(r._p.link);
console.log("Restoring " + r._p.link);
try
{
var ar = await this.#sendRequest(IIPPacketAction.QueryLink)
.addUint16(link.length)
.addUint8Array(link)
.done();
var dataType = ar[0];
var data = ar[1];
if (dataType.identifier == TransmissionTypeIdentifier.ResourceList
|| dataType.identifier == TransmissionTypeIdentifier.List)
{
// remove from suspended.
this.#suspendedResources.remove(r._p.instanceId);
// parse them as int
var id = data.getUint32(8);
// id changed ?
if (id != r._p.instanceId)
r._p.instanceId = id;
this.#neededResources.set(id, r);
await this.fetch(id, null);
console.log("Restored " + id);
}
}
catch (ex)
{
if (ex.code == ExceptionCode.ResourceNotFound)
{
// skip this resource
}
else
{
break;
}
}
}
}
catch (ex) {
console.log(ex);
}
}
catch (ex) {
return false;
}
this._emit("resumed", this);
return true;
}
// hold() {
// this.holdSending = true;
// }
// unhold() {
// if (this.holdSending) {
// this.holdSending = false;
// var msg = this.sendBuffer.read();
// if (msg == null || msg.length == 0)
// return;
// this.socket.sendAll(msg);
// }
// }
trigger(trigger) {
if (trigger == ResourceTrigger.Open) {
if (this.server != null)
return new AsyncReply(true);
var { domain = null,
secure = false,
username = null,
password = null,
checkInterval = 30,
connectionTimeout = 600,
revivingTime = 120,
tokenIndex = 0,
token = null,
debug = false,
autoReconnect = false,
keepAliveInterval = 30,
keepAliveTime = 10,
reconnectInterval = 5,
authenticator = null} = this.instance.attributes.toObject();
this.authenticator = authenticator;
this.debug = debug;
this.checkInterval = checkInterval * 1000; // check every 30 seconds
this.connectionTimeout = connectionTimeout * 1000; // 10 minutes (4 pings failed)
this.revivingTime = revivingTime * 1000; // 2 minutes
this.autoReconnect = autoReconnect;
this.reconnectInterval = reconnectInterval;
this.keepAliveInterval = keepAliveInterval;
this.keepAliveTime = keepAliveTime;
var host = this.instance.name.split(':');
var address = host[0];
var port = host.length > 1 ? parseInt(host[1]) : 10518;
if (username != null
&& password != null)
{
var pw = DC.stringToBytes(password);
return this.connect(AuthenticationMethod.Credentials, null, address, port, username, null, pw, domain, secure);
}
else if (token != null)
{
var tk = token instanceof Uint8Array ? token : DC.stringToBytes(token);
return this.connect(AuthenticationMethod.Token, null, address, port, null, tokenIndex, tk, domain, secure);
}
else
{
return this.connect(AuthenticationMethod.None, null, address, port, null, 0, null, domain, secure);
}
}
return new AsyncReply(true);
}
connect(method = AuthenticationMethod.Certificate, socket = null, hostname = null, port = 0,
username = null, tokenIndex = 0, passwordOrToken = null, domain = null, secure = false)
{
if (this.#openReply != null)
throw new AsyncException(ErrorType.Exception, 0, "Connection in progress");
this.#status = ConnectionStatus.Connecting;
this.#openReply = new AsyncReply();
if (hostname != null) {
this.#session = new Session();
this.#session.authenticationType = AuthenticationType.Client;
this.#session.localMethod = method;
this.#session.remoteMethod = AuthenticationMethod.None;
this.#session.localHeaders.set(IIPAuthPacketHeader.Domain, domain);
this.#session.localHeaders.set(IIPAuthPacketHeader.Nonce, Global.generateBytes(32));
if (method == AuthenticationMethod.Credentials)
{
this.#session.localHeaders.set(IIPAuthPacketHeader.Username, username);
}
else if (method == AuthenticationMethod.Token)
{
this.#session.localHeaders.set(IIPAuthPacketHeader.TokenIndex, tokenIndex);
}
else if (method == AuthenticationMethod.Certificate)
{
throw Exception("Unsupported authentication method.");
}
this.#localPasswordOrToken = passwordOrToken;
this.#invalidCredentials = false;
}
if (this.session == null)
throw new AsyncException(ErrorType.Exception, 0, "Session not initialized");
if (socket == null)
socket = new WSocket();// TCPSocket();
if (port > 0)
this.#port = port;
if (hostname != null)
this.#hostname = hostname;
if (secure != null)
this.#secure = secure;
this.#connectSocket(socket);
return this.#openReply;
}
#connectSocket(socket){
let self = this;
socket.connect(this.#hostname, this.#port, this.#secure).then(x =>
{
self.assign(socket);
}).error((x) =>
{
if (self.autoReconnect){
console.log("Reconnecting socket...");
setTimeout(() => {
self.#connectSocket(socket);
}, self.reconnectInterval * 1000);
} else {
self.#openReply?.triggerError(x);
self.#openReply = null;
}
});
}
#declare() {
if (this.#session.localMethod == AuthenticationMethod.Credentials
&& this.#session.remoteMethod == AuthenticationMethod.None)
{
// change to Map<byte, object> for compatibility
let headers = Codec.compose(this.#session.localHeaders, this);
// declare (Credentials -> No Auth, No Enctypt)
this.#sendParams()
.addUint8(IIPAuthPacketInitialize.CredentialsNoAuth)
.addDC(headers)
.done();
}
else if (this.#session.localMethod == AuthenticationMethod.Token
&& this.#session.remoteMethod == AuthenticationMethod.None)
{
// change to Map<byte, object> for compatibility
let headers = Codec.compose(session.localHeaders, this);
this.#sendParams()
.addUint8(IIPAuthPacketInitialize.TokenNoAuth)
.addDC(headers)
.done();
}
else if (session.localMethod == AuthenticationMethod.None
&& session.remoteMethod == AuthenticationMethod.None)
{
// change to Map<byte, object> for compatibility
let headers = Codec.compose(session.localHeaders, this);
// @REVIEW: MITM Attack can still occure
this.#sendParams()
.addUint8(IIPAuthPacketInitialize.NoAuthNoAuth)
.addDC(headers)
.done();
}
else
{
throw new Exception("Authentication method is not implemented.");
}
}
assign(socket)
{
this.#socket = socket;
socket.receiver = this;
// @TODO: add referer
// this.#session.LocalHeaders[IIPAuthPacketHeader.IPv4] = socket.remoteEndPoint.Address.Address;
if (socket.state == SocketState.Established &&
this.#session.authenticationType == AuthenticationType.Client)
{
this.#declare();
}
}
#unsubscribeAll()
{
for (let resource of this.#subscriptions.keys()) {
resource.instance.off("EventOccurred", this.#instance_eventOccurred, this);
resource.instance.off("PropertyModified", this.#instance_propertyModified, this);
resource.instance.off("ResourceDestroyed", this.#instance_resourceDestroyed, this);
}
this.#subscriptions.clear();
}
destroy(){
this.#unsubscribeAll();
super.destroy();
}
networkClose(socket)
{
// clean up
this.#ready = false;
this.#status = ConnectionStatus.Closed;
this.#readyToEstablish = false;
clearTimeout(this.#keepAliveTimer);
try
{
this.#requests.values.forEach((x) => {
try {
x.triggerError(new AsyncException(ErrorType.Management, 0, "Connection closed"));
} catch (ex) { }
});
this.#resourceRequests.values.forEach((x) => {
try {
x.triggerError(new AsyncException(ErrorType.Management, 0, "Connection closed"));
} catch (ex) { }
});
this.#templateRequests.values.forEach((x) => {
try {
x.triggerError(new AsyncException(ErrorType.Management, 0, "Connection closed"));
} catch (ex) { }
});
}
catch(ex)
{
// unhandled error
}
this.#requests.clear();
this.#resourceRequests.clear();
this.#templateRequests.clear();
for (let x of this.#attachedResources.values)
{
let r = x.deref();
if (r != null){
r._suspend();
this.#suspendedResources.set(r._p.instanceId, x);
}
}
if (this.server != null) {
this.#suspendedResources.clear();
this.#unsubscribeAll();
Warehouse.remove(this);
if (this.ready)
this.server.membership.logout(this.session);
}
else if (this.autoReconnect && !this.#invalidCredentials){
let self = this;
setTimeout(() => self.reconnect(), this.reconnectInterval * 1000);
}
else {
this.#suspendedResources.clear();
}
this.#attachedResources.clear();
this._emit("close", this);
}
networkConnect(socket)
{
if (this.session.localAuthentication.Type == AuthenticationType.Client)
this.#declare();
this._emit("connect", this);
}
networkReceive(sender, buffer)
{
try
{
// Unassigned ?
if (this.#socket == null)
return;
// Closed ?
if (this.#socket.state == SocketState.Closed)
return;
//this.lastAction = DateTime.Now;
if (!this.processing)
{
this.processing = true;
try
{
while (buffer.available > 0 && !buffer.protected)
{
//console.log("RX", buffer.length );
this.#dataReceived(buffer);
}
}
catch
{
}
this.processing = false;
}
}
catch (ex)
{
console.log(ex);
//Global.Log("NetworkConnection", LogType.Warning, ex.ToString());
}
}
put(resource) {
if (Codec.isLocalResource(resource, this))
this.#neededResources.add(resource._p.instanceId, resource);
return new AsyncReply(true);
}
remove(resource) {
// nothing to do (IStore interface)
}
// Protocol Implementation
#sendRequest(action) {
var reply = new AsyncReply();
this.#callbackCounter++;
this.#requests.set(this.#callbackCounter, reply);
return this.#sendParams(reply)
.addUint8(0x40 | action)
.addUint32(this.#callbackCounter);
}
_sendDetachRequest(instanceId)
{
try
{
if (this.#attachedResources.containsKey(instanceId))
this.#attachedResources.remove(instanceId);
if (this.#suspendedResources.containsKey(instanceId))
this.#suspendedResources.remove(instanceId);
return this.#sendRequest(IIPPacketAction.DetachResource)
.addUint32(instanceId)
.done();
}
catch(ex)
{
return null;
}
}
_sendInvoke(instanceId, index, parameters) {
var reply = new AsyncReply();
var pb = Codec.compose(parameters, this);
let callbackId = ++this.#callbackCounter;
this.#sendParams()
.addUint8(0x40 | IIPPacketAction.InvokeFunction)
.addUint32(callbackId)
.addUint32(instanceId)
.addUint8(index)
.addUint8Array(pb)
.done();
this.#requests.set(callbackId, reply);
return reply;
}
_sendSetProperty(instanceId, index, value){
var cv = Codec.compose(value, this);
return this.#sendRequest(IIPPacketAction.SetProperty)
.addUint32(instanceId)
.addUint8(index)
.addUint8Array(cv)
.done()
}
#sendError(type, callbackId, errorCode, errorMessage = "") {
var msg = DC.stringToBytes(errorMessage);
if (type == ErrorType.Management)
this.#sendParams()
.addUint8(0xC0 | IIPPacketReport.ManagementError)
.addUint32(callbackId)
.addUint16(errorCode)
.done();
else if (type == ErrorType.Exception)
this.#sendParams()
.addUint8(0xC0 | IIPPacketReport.ExecutionError)
.addUint32(callbackId)
.addUint16(errorCode)
.addUint16(msg.length)
.addUint8Array(msg)
.done();
}
#sendProgress(callbackId, value, max) {
this.#sendParams()
.addUint8(0xC0 | IIPPacketReport.ProgressReport)
.addUint32(callbackId)
.addInt32(value)
.addInt32(max)
.done();
}
#sendChunk(callbackId, chunk) {
var c = Codec.compose(chunk, this);
this.#sendParams()
.addUint8(0xC0 | IIPPacketReport.ChunkStream)
.addUint32(callbackId)
.addUint8Array(c)
.done();
}
IIPReply(callbackId) {
var results = Array.prototype.slice.call(arguments, 1);
var req = this.#requests.item(callbackId);
this.#requests.remove(callbackId);
req.trigger(results);
}
IIPReplyInvoke(callbackId, dataType, data) {
var req = this.#requests.item(callbackId);
if (req != null) {
this.#requests.remove(callbackId);
Codec.parse(data, 0, this, null, dataType).reply.then(function (rt) {
req.trigger(rt);
});
}
}
IIPReportError(callbackId, errorType, errorCode, errorMessage) {
var req = this.#requests.item(callbackId);
if (req != null)
{
this.#requests.remove(callbackId);
req.triggerError(errorType, errorCode, errorMessage);
}
}
IIPReportProgress(callbackId, type, value, max) {
var req = this.#requests.item(callbackId);
if (req != null)
req.triggerProgress(type, value, max);
}
IIPReportChunk(callbackId, dataType, data) {
var req = this.#requests.item(callbackId);
if (req != null) {
Codec.parse(data, 0, this, null, dataType).reply.then(function (x) {
req.triggerChunk(x);
});
}
}
IIPEventResourceReassigned(resourceId, newResourceId) {
}
IIPEventResourceDestroyed(resourceId) {
if (this.#attachedResources.contains(resourceId))
{
let r = this.#attachedResources.get(resourceId).deref();
r?.destroy();
this.#attachedResources.remove(resourceId);
}
else if (this.#neededResources.contains(resourceId))
{
// @TODO: handle this mess
this.#neededResources.remove(resourceId);
}
}
IIPEventPropertyUpdated(resourceId, index, dataType, data) {
let self = this;
this.fetch(resourceId, null).then(function (r) {
let pt = r.instance.template.getPropertyTemplateByIndex(index);
if (pt == null)
return; // ft found, fi not found, this should never happen
// push to the queue to gaurantee serialization
let item = new AsyncReply();
self.#queue.add(item);
Codec.parse(data, 0, self, null, dataType).reply.then(function (args) {
item.trigger(new DistributedResourceQueueItem(r, DistributedResourceQueueItemType.Propery, args, index));
}).error(function (ex) {
self.#queue.remove(item);
console.log("Esiur Property Error", ex);
});
});
}
IIPEventEventOccurred(resourceId, index, dataType, data) {
let self = this;
this.fetch(resourceId, null).then(function (r) {
let et = r.instance.template.getEventTemplateByIndex(index);
if (et == null)
return; // ft found, fi not found, this should never happen
// push to the queue to guarantee serialization
var item = new AsyncReply();
self.#queue.add(item);
// Codec.parseVarArray(content, 0, content.length, self).then(function (args) {
Codec.parse(data, 0, self, null, dataType).reply.then(function (args) {
item.trigger(new DistributedResourceQueueItem(r, DistributedResourceQueueItemType.Event, args, index));
}).error(function (ex) {
self.#queue.remove(item);
console.log("Esiur Event Error", ex);
});
});
}
IIPEventChildAdded(resourceId, childId) {
var self = this;
this.fetch(resourceId, null).then(function (parent) {
self.fetch(childId, null).then(function (child) {
parent.instance.children.add(child);
});
});
}
IIPEventChildRemoved(resourceId, childId) {
var self = this;
this.fetch(resourceId, null).then(function (parent) {
self.fetch(childId, null).then(function (child) {
parent.instance.children.remove(child);
});
});
}
IIPEventRenamed(resourceId, name) {
this.fetch(resourceId, null).then(function (resource) {
resource.instance.attributes.set("name", name);
});
}
IIPEventAttributesUpdated(resourceId, attributes) {
var self = this;
this.fetch(resourceId, null).then(function (resource) {
var attrs = attributes.getStringArray(0, attributes.length);
self.getAttributes(resource, attrs).then(function (s) {
resource.instance.setAttributes(s);
});
});
}
#sendReply(action, callbackId) {
return this.#sendParams().addUint8(0x80 | action).addUint32(callbackId);
}
#sendEvent(evt) {
return this.#sendParams().addUint8(evt);
}
_sendListenRequest(instanceId, index)
{
var reply = new AsyncReply();
let callbackId = ++this.#callbackCounter;
this.#sendParams()
.addUint8(0x40 | IIPPacketAction.Listen)
.addUint32(callbackId)
.addUint32(instanceId)
.addUint8(index)
.done();
this.#requests.set(callbackId, reply);
return reply;
}
_sendUnlistenRequest(instanceId, index)
{
var reply = new AsyncReply();
let callbackId = ++this.#callbackCounter;
this.#sendParams()
.addUint8(0x40 | IIPPacketAction.Unlisten)
.addUint32(callbackId)
.addUint32(instanceId)
.addUint8(index)
.done();
this.#requests.set(callbackId, reply);
return reply;
}
IIPRequestAttachResource(callback, resourceId) {
//var sl = this.#sendParams();
var self = this;
Warehouse.getById(resourceId).then(function (r) {
if (r != null) {
if (r.instance.applicable(self.#session, ActionType.Attach, null) == Ruling.Denied) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.AttachDenied);
return;
}
self.#unsubscribe(r);
// reply ok
var link = DC.stringToBytes(r.instance.link);
if (r instanceof DistributedResource)
self.#sendReply(IIPPacketAction.AttachResource, callback)
.addUint8Array(r.instance.template.classId.value)
.addUint64(r.instance.age)
.addUint16(link.length)
.addUint8Array(link)
.addUint8Array(Codec.compose(r._serialize(), self))
.done();
else
self.#sendReply(IIPPacketAction.AttachResource, callback)
.addUint8Array(r.instance.template.classId.value)
.addUint64(r.instance.age)
.addUint16(link.length)
.addUint8Array(link)
.addUint8Array(Codec.compose(r.instance.serialize(), self))
.done();
self.#subscribe(r);
}
else {
// reply failed
self.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceNotFound);
}
});
}
#subscribe(resource)
{
resource.instance.on("EventOccurred", this.#instance_eventOccurred, this);
resource.instance.on("PropertyModified", this.#instance_propertyModified, this);
resource.instance.on("ResourceDestroyed", this.#instance_resourceDestroyed, this);
this.#subscriptions.set(resource, []);
}
#unsubscribe(resource)
{
resource.instance.off("EventOccurred", this.#instance_eventOccurred, this);
resource.instance.off("PropertyModified", this.#instance_propertyModified, this);
resource.instance.off("ResourceDestroyed", this.#instance_resourceDestroyed, this);
this.#subscriptions.delete(resource);
}
IIPRequestReattachResource(callback, resourceId, resourceAge) {
var self = this;
Warehouse.getById(resourceId).then(function (r) {
if (r != null) {
self.#unsubscribe(r);
self.#subscribe(r);
// reply ok
self.#sendReply(IIPPacketAction.ReattachResource, callback)
.addUint64(r.instance.age)
.addUint8Array(Codec.compose(r.instance.serialize(), self))
.done();
}
else {
// reply failed
self.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceNotFound);
}
});
}
IIPRequestDetachResource(callback, resourceId) {
var self = this;
Warehouse.getById(resourceId).then(function (r) {
if (r != null) {
self.#unsubscribe(r);
// reply ok
self.#sendReply(IIPPacketAction.DetachResource, callback).done();
}
else {
// reply failed
self.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceNotFound);
}
});
}
IIPRequestCreateResource(callback, storeId, parentId, content) {
var self = this;
Warehouse.getById(storeId).then(function (store) {
if (store == null) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.StoreNotFound);
return;
}
if (!(store instanceof IStore)) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceIsNotStore);
return;
}
// check security
if (store.instance.applicable(self.#session, ActionType.CreateResource, null) != Ruling.Allowed) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.CreateDenied);
return;
}
Warehouse.getById(parentId).then(function (parent) {
// check security
if (parent != null)
if (parent.instance.applicable(self.#session, ActionType.AddChild, null) != Ruling.Allowed) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.AddChildDenied);
return;
}
var offset = 0;
var className = content.getString(offset + 1, content[0]);
offset += 1 + content[0];
var nameLength = content.getUint16(offset);
offset += 2;
var name = content.getString(offset, nameLength);
var cl = content.getUint32(offset);
offset += 4;
var type = window[className];
if (type == null) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.ClassNotFound);
return;
}
DataDeserializer.listParser(content, offset, cl, self, null).then(function (parameters) {
offset += cl;
cl = content.getUint32(offset);
DataDeserializer.typedMapParser(content, offset, cl, self, null).then(function (attributes) {
offset += cl;
cl = content.length - offset;
DataDeserializer.typedMapParser(content, offset, cl, self, null).then(function (values) {
var resource = new (Function.prototype.bind.apply(type, values));
Warehouse.put(name, resource, store, parent).then(function(ok){
self.#sendReply(IIPPacketAction.CreateResource, callback)
.addUint32(resource.Instance.Id)
.done();
}).error(function(ex){
// send some error
self.#sendError(ErrorType.Management, callback, ExceptionCode.AddToStoreFailed);
});
});
});
});
});
});
}
IIPRequestDeleteResource(callback, resourceId) {
var self = this;
Warehouse.getById(resourceId).then(function (r) {
if (r == null) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceNotFound);
return;
}
if (r.instance.store.instance.applicable(session, ActionType.Delete, null) != Ruling.Allowed) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.DeleteDenied);
return;
}
if (Warehouse.remove(r))
self.#sendReply(IIPPacketAction.DeleteResource, callback).done();
else
self.#sendError(ErrorType.Management, callback, ExceptionCode.DeleteFailed);
});
}
IIPRequestLinkTemplates(callback, resourceLink)
{
var queryCallback = (r) =>
{
if (r == null)
this.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceNotFound);
else
{
var list = r.filter(x => x.instance.applicable(this.session, ActionType.ViewTemplate, null) != Ruling.Denied);
if (list.length == 0)
this.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceNotFound);
else
{
// get all templates related to this resource
var msg = BL();
var templates = [];
for (var i = 0; i < list.length; i++)
templates = templates
.concat(TypeTemplate.getDependencies(list[i].instance.template)
.filter(x => !templates.includes(x)));
for(var i = 0; i < templates.length; i++) {
msg.addInt32(templates[i].content.length)
.addUint8Array(templates[i].content);
}
// send
this.#sendReply(IIPPacketAction.LinkTemplates, callback)
.addDC(TransmissionType.compose(TransmissionTypeIdentifier.RawData, msg))
.done();
}
}
};
if (this.server?.entryPoint != null)
this.server.entryPoint.query(resourceLink, this).then(queryCallback);
else
Warehouse.query(resourceLink).then(queryCallback);
}
IIPRequestTemplateFromClassName(callback, className) {
var self = this;
var t = Warehouse.getTemplateByClassName(className);
if (t != null) {
self.#sendReply(IIPPacketAction.TemplateFromClassName, callback)
.addUint32(t.content.length)
.addUint8Array(t.content)
.done();
} else {
// reply failed
self.#sendError(ErrorType.Management, callback, ExceptionCode.TemplateNotFound);
}
}
IIPRequestTemplateFromClassId(callback, classId) {
var self = this;
var t = Warehouse.getTemplateByClassId(classId);
if (t != null)
self.#sendReply(IIPPacketAction.TemplateFromClassId, callback)
.addDC(TransmissionType.compose(
TransmissionTypeIdentifier.RawData, t.content))
.done();
else {
// reply failed
self.#sendError(ErrorType.Management, callback, ExceptionCode.TemplateNotFound);
}
}
IIPRequestTemplateFromResourceId(callback, resourceId) {
var self = this;
Warehouse.getById(resourceId).then(function (r) {
if (r != null)
self.#sendReply(IIPPacketAction.TemplateFromResourceId, callback)
.addDC(TransmissionType.compose(
TransmissionTypeIdentifier.RawData, r.instance.template.content))
.done();
else {
// reply failed
self.#sendError(ErrorType.Management, callback, ExceptionCode.TemplateNotFound);
}
});
}
IIPRequestProcedureCall(callback, procedureCall, transmissionType, content)
{
if (this.server == null)
{
this.#sendError(ErrorType.Management, callback, ExceptionCode.GeneralFailure);
return;
}
var call = this.server.calls.get(procedureCall);
if (call == null)
{
this.#sendError(ErrorType.Management, callback, ExceptionCode.MethodNotFound);
return;
}
let parsed = Codec.parse(content, 0, this, null, transmissionType);
parsed.Then(results =>
{
// un hold the socket to send data immediately
this.#socket.unhold();
// @TODO: Make managers for procedure calls
//if (r.Instance.Applicable(session, ActionType.Execute, ft) == Ruling.Denied)
//{
// SendError(ErrorType.Management, callback,
// (ushort)ExceptionCode.InvokeDenied);
// return;
//}
this.#invokeFunction(call.method, callback, results, IIPPacketAction.ProcedureCall, call.target);
}).error(x =>
{
this.#sendError(ErrorType.Management, callback, ExceptionCode.ParseError);
});
}
IIPRequestStaticCall(callback, classId, index, transmissionType, content)
{
let template = Warehouse.getTemplateByClassId(classId);
if (template == null)
{
this.#sendError(ErrorType.Management, callback, ExceptionCode.TemplateNotFound);
return;
}
let ft = template.getFunctionTemplateByIndex(index);
if (ft == null)
{
// no function at this index
this.#sendError(ErrorType.Management, callback, ExceptionCode.MethodNotFound);
return;
}
let parsed = Codec.parse(content, 0, this, null, transmissionType);
parsed.then(results =>
{
// un hold the socket to send data immediately
this.#socket.unhold();
var fi = ft.methodInfo;
if (fi == null)
{
// ft found, fi not found, this should never happen
this.#sendError(ErrorType.Management, callback, ExceptionCode.MethodNotFound);
return;
}
// @TODO: Make managers for static calls
//if (r.Instance.Applicable(session, ActionType.Execute, ft) == Ruling.Denied)
//{
// SendError(ErrorType.Management, callback,
// (ushort)ExceptionCode.InvokeDenied);
// return;
//}
this.#invokeFunction(fi, callback, results, IIPPacketAction.StaticCall, null);
}).error(x =>
{
this.#sendError(ErrorType.Management, callback, ExceptionCode.ParseError);
});
}
IIPRequestInvokeFunction(callback, resourceId, index, dataType, data) {
let self = this;
Warehouse.getById(resourceId).then(function (r) {
if (r == null) {
this.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceNotFound);
return;
}
let ft = r.instance.template.getFunctionTemplateByIndex(index);
if (ft == null)
{
// no function at this index
this.#sendError(ErrorType.Management, callback, ExceptionCode.MethodNotFound);
return;
}
Codec.parse(data, 0, self, null, dataType).reply.then(function (args) {
if (r instanceof DistributedResource) {
var rt = r._invoke(index, args);
if (rt != null) {
rt.then(function (res) {
self.#sendReply(IIPPacketAction.InvokeFunction, callback)
.addUint8Array(Codec.compose(res, self))
.done();
});
}
else {
// function not found on a distributed object
this.#sendError(ErrorType.Management, callback, ExceptionCode.MethodNotFound);
return;
}
}
else
{
var fi = r[ft.name];
if (!(fi instanceof Function)) {
// ft found, fi not found, this should never happen
this.#sendError(ErrorType.Management, callback, ExceptionCode.MethodNotFound);
return;
}
if (r.instance.applicable(self.#session, ActionType.Execute, ft) == Ruling.Denied) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.InvokeDenied);
return;
}
self.#invokeFunction(fi, callback, args, IIPPacketAction.InvokeFunction, r);
}
});
});
}
#invokeFunction(fi, callback, parameters, actionType, target = null)
{
let self = this;
let indexedArgs = [];
for(let [k,v] of parameters.entries())
indexedArgs[k] = v;
indexedArgs.push(self);
let rt;
try
{
rt = fi.apply(target, indexedArgs);
}
catch(ex)
{
this.#sendError(ErrorType.Exception, callback, 0, ex.toString());
return;
}
// Is iterator ?
if (rt != null && rt[Symbol.iterator] instanceof Function) {
for (let v of rt)
this.#sendChunk(callback, v);
this.#sendReply(actionType, callback)
.addUint8(DataType.Void)
.done();
}
else if (rt instanceof AsyncReply) {
rt.then(function (res) {
self.#sendReply(actionType, callback)
.addUint8Array(Codec.compose(res, self))
.done();
}).error(ex => {
self.#sendError(ErrorType.Exception, callback, ex.code, ex.message);
}).progress((pt, pv, pm) =>
{
self.#sendProgress(callback, pv, pm);
}).chunk(v =>
{
self.#sendChunk(callback, v);
});
}
else if (rt instanceof Promise)
{
rt.then(function (res) {
self.#sendReply(actionType, callback)
.addUint8Array(Codec.compose(res, self))
.done();
}).catch(ex => {
self.#sendError(ErrorType.Exception, callback, 0, ex.toString());
});
}
else {
self.#sendReply(actionType, callback)
.addUint8Array(Codec.compose(rt, self))
.done();
}
}
// IIPRequestGetProperty(callback, resourceId, index) {
// var self = this;
// Warehouse.getById(resourceId).then(function (r) {
// if (r != null) {
// var pt = r.instance.template.getFunctionTemplateByIndex(index);
// if (pt != null) {
// if (r instanceof DistributedResource) {
// self.#sendReply(IIPPacketAction.GetProperty, callback)
// .addUint8Array(Codec.compose(r._get(pt.index), self))
// .done();
// }
// else {
// var pv = r[pt.name];
// self.#sendReply(IIPPacketAction.GetProperty)
// .addUint8Array(Codec.compose(pv, self))
// .done();
// }
// }
// else {
// // pt not found
// }
// }
// else {
// // resource not found
// }
// });
// }
// IIPRequestGetPropertyIfModifiedSince(callback, resourceId, index, age) {
// var self = this;
// Warehouse.getById(resourceId).then(function (r) {
// if (r != null) {
// var pt = r.instance.template.getFunctionTemplateByIndex(index);
// if (pt != null) {
// if (r.instance.getAge(index) > age) {
// var pv = r[pt.name];
// self.#sendReply(IIPPacketAction.GetPropertyIfModified, callback)
// .addUint8Array(Codec.compose(pv, self))
// .done();
// }
// else {
// self.#sendReply(IIPPacketAction.GetPropertyIfModified, callback)
// .addUint8(DataType.NotModified)
// .done();
// }
// }
// else {
// // pt not found
// }
// }
// else {
// // resource not found
// }
// });
// }
IIPRequestListen(callback, resourceId, index)
{
let self = this;
Warehouse.getById(resourceId).then((r) =>
{
if (r != null)
{
var et = r.instance.template.getEventTemplateByIndex(index);
if (et != null)
{
if (r instanceof DistributedResource)
{
r.listen(et).then(x =>
{
self.#sendReply(IIPPacketAction.Listen, callback).done();
}).error(x => self.#sendError(ErrorType.Exception, callback, ExceptionCode.GeneralFailure));
}
else
{
if (!self.#subscriptions.has(r))
{
self.#sendError(ErrorType.Management, callback, ExceptionCode.NotAttached);
return;
}
if (self.#subscriptions.get(r).includes(index))
{
self.#sendError(ErrorType.Management, callback, ExceptionCode.AlreadyListened);
return;
}
self.#subscriptions.get(r).push(index);
self.#sendReply(IIPPacketAction.Listen, callback).done();
}
}
else
{
// pt not found
self.#sendError(ErrorType.Management, callback, ExceptionCode.MethodNotFound);
}
}
else
{
// resource not found
self.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceNotFound);
}
});
}
IIPRequestUnlisten(callback, resourceId, index)
{
let self = this;
Warehouse.getById(resourceId).then((r) =>
{
if (r != null)
{
var et = r.instance.template.getEventTemplateByIndex(index);
if (et != null)
{
if (r instanceof DistributedResource)
{
r.unlisten(et).then(x =>
{
self.#sendReply(IIPPacketAction.Unlisten, callback).done();
}).error(x => self.#sendError(ErrorType.Exception, callback, ExceptionCode.GeneralFailure));
}
else
{
if (!self.#subscriptions.has(r))
{
self.#sendError(ErrorType.Management, callback, ExceptionCode.NotAttached);
return;
}
if (!self.#subscriptions.get(r).includes(index))
{
self.#sendError(ErrorType.Management, callback, ExceptionCode.AlreadyUnlistened);
return;
}
let ar = self.#subscriptions.get(r);
let i = ar.indexOf(index);
ar.splice(i, 1);
self.#sendReply(IIPPacketAction.Unlisten, callback).done();
}
}
else
{
// pt not found
self.#sendError(ErrorType.Management, callback, ExceptionCode.MethodNotFound);
}
}
else
{
// resource not found
self.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceNotFound);
}
});
}
IIPRequestSetProperty(callback, resourceId, index, dataType, data) {
var self = this;
Warehouse.getById(resourceId).then(function (r) {
if (r != null) {
var pt = r.instance.template.getPropertyTemplateByIndex(index);
if (pt != null) {
Codec.parse(data, 0, self, null, dataType).reply.then(function (value) {
if (r instanceof DistributedResource) {
// propagation
r._set(index, value).then(function (x) {
self.#sendReply(IIPPacketAction.SetProperty, callback)
.done();
}).error(function (x) {
self.#sendError(x.type, callback, x.code, x.message);
});
}
else {
if (r.instance.applicable(self.#session, ActionType.SetProperty, pt) == Ruling.Denied) {
self.#sendError(AsyncReply.ErrorType.Exception, callback, ExceptionCode.SetPropertyDenied);
return;
}
try {
if (r[pt.name] instanceof DistributedPropertyContext)
value = new DistributedPropertyContext(this, value);
r[pt.name] = value;
self.#sendReply(IIPPacketAction.SetProperty, callback).done();
}
catch (ex) {
self.#sendError(AsyncReply.ErrorType.Exception, callback, 0, ex.toString());
}
}
});
}
else {
// property not found
self.#sendError(AsyncReply.ErrorType.Management, callback, ExceptionCode.PropertyNotFound);
}
}
else {
// resource not found
self.#sendError(AsyncReply.ErrorType.Management, callback, ExceptionCode.PropertyNotFound);
}
});
}
IIPRequestInquireResourceHistory(callback, resourceId, fromDate, toDate) {
var self = this;
Warehouse.getById(resourceId).then(function (r) {
if (r != null) {
r.instance.store.getRecord(r, fromDate, toDate).then(function (results) {
var history = Codec.composeHistory(results, self, true);
self.#sendReply(IIPPacketAction.ResourceHistory, callback)
.addUint8Array(history)
.done();
});
}
});
}
IIPRequestQueryResources(callback, resourceLink) {
var self = this;
let queryCallback = function (resources) {
if (resources == null)
self.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceNotFound);
else
{
var list = resources.filter(function (r) { return r.instance.applicable(self.#session, ActionType.Attach, null) != Ruling.Denied });
if (list.length == 0)
self.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceNotFound);
else
self.#sendReply(IIPPacketAction.QueryLink, callback)
.addUint8Array(Codec.compose(list, self))
.done();
}
};
if (this.server?.entryPoint != null)
{
this.server?.entryPoint.query(resourceLink, this).then(queryCallback);
}
else
{
Warehouse.query(resourceLink).then(queryCallback);
}
}
create(store, parent, className, parameters, attributes, values) {
var reply = new AsyncReply();
var sb = DC.stringToBytes(className);
var pkt = BL().addUint32(store.instance.id)
.addUint32(parent.instance.id)
.addUint32(sb.length)
.addUint8Array(sb)
.addUint8Array(Codec.composeVarArray(parameters, this, true))
.addUint8Array(Codec.composeStructure(attributes, this, true, true, true))
.addUint8Array(Codec.composeStructure(values, this));
pkt.addUint32(pkt.length, 8);
this.#sendRequest(IIPPacketAction.CreateResource).addUint8Array(pkt.ToArray()).done().then(function (args) {
var rid = args[0];
self.fetch(rid, null).then(function (r) {
reply.trigger(r);
});
});
return reply;
}
query(resourceLink) {
var reply = new AsyncReply();
var self = this;
var sb = DC.stringToBytes(resourceLink);
this.#sendRequest(IIPPacketAction.QueryLink)
.addUint16(sb.length)
.addUint8Array(sb)
.done()
.then(function (ar) {
let dataType = ar[0];
let data = ar[1];
Codec.parse(data, 0, self, null, dataType)
.reply.then((resources) => {
reply.trigger((resources))
}).error((ex) => reply.triggerError(ex));
}).error(function (ex) {
reply.triggerError(ex);
});
return reply;
}
getTemplateByClassName(className) {
let templates = this.#templates.filter({ className: className });
if (templates.length > 0)
return new AsyncReply(templates[0]);
else if (this.#templateByNameRequests.contains(className))
return this.#templateByNameRequests.item(className);
var reply = new AsyncReply();
this.#templateByNameRequests.add(className, reply);
var self = this;
let classNameBytes = DC.stringToBytes(className);
this.#sendRequest(IIPPacketAction.TemplateFromClassName)
.addUint8(classNameBytes.length)
.addUint8Array(classNameBytes)
.done()
.then(function (rt) {
self.#templateByNameRequests.remove(className);
self.#templates.add(rt[0].classId.valueOf(), rt[0]);
Warehouse.putTemplate(rt[0]);
reply.trigger(rt[0]);
});
return reply;
}
getTemplate(classId) {
if (this.#templates.contains(classId))
return new AsyncReply(this.#templates.item(classId));
else if (this.#templateRequests.contains(classId))
return this.#templateRequests.item(classId);
var reply = new AsyncReply();
this.#templateRequests.add(classId.valueOf(), reply);
var self = this;
this.#sendRequest(IIPPacketAction.TemplateFromClassId)
.addUint8Array(classId.value)
.done()
.then(function (rt) {
self.#templateRequests.remove(classId);
self.#templates.add(rt[0].classId.valueOf(), rt[0]);
Warehouse.putTemplate(rt[0]);
reply.trigger(rt[0]);
});
return reply;
}
// IStore interface
get(path) {
var rt = new AsyncReply();
this.query(path).then(function (ar) {
if (ar != null && ar.length > 0)
rt.trigger(ar[0]);
else
rt.trigger(null);
}).error(function (ex) { rt.triggerError(ex); });
return rt;
/*
if (this.pathRequests[path])
return this.pathRequests[path];
var reply = new AsyncReply();
this.pathRequests[path] = reply;
var bl = new BinaryList();
bl.addString(path);
bl.addUint16(bl.length, 0);
var link = data.get
var self = this;
this.#sendRequest(IIPPacketAction.ResourceIdFromResourceLink)
.addUint16(.then(function (rt) {
delete self.pathRequests[path];
self.fetch(rt[1]).then(function (r) {
reply.trigger(r);
});
});
return reply;
*/
}
// retrieve(iid) {
// let r = this.resources.item(iid);
// return new AsyncReply(r);
// //for (var r in this.resources)
// // if (this.resources[r].instance.id == iid)
// // return new AsyncReply(r);
// //return new AsyncReply(null);
// }
getLinkTemplates(link)
{
var reply = new AsyncReply();
var l = DC.stringToBytes(link);
this.#sendRequest(IIPPacketAction.LinkTemplates)
.addUint16(l.length)
.addUint8Array(l)
.done()
.then((rt) =>
{
var templates = [];
// parse templates
let tt = rt[0];
let data = rt[1] ;
//var offset = 0;
for (var offset = tt.offset; offset < tt.contentLength;) {
var cs = data.getUint32(offset);
offset += 4;
templates.push(TypeTemplate.parse(data, offset, cs));
offset += cs;
}
reply.trigger(templates);
}).error((ex) =>
{
reply.triggerError(ex);
});
return reply;
}
// Get a resource from the other end
fetch(id, requestSequence) {
let resource = this.#attachedResources.item(id)?.deref();
if (resource != null)
return new AsyncReply(resource);
resource = this.#neededResources.item(id);
let request = this.#resourceRequests.item(id);
if (request != null) {
if (resource != null && (requestSequence?.includes(id) ?? false))
return new AsyncReply(resource);
else
return request;
}
else if (resource != null && !resource._p.suspended) {
// @REVIEW: this should never happen
console.log("DCON", LogType.Error, "Resource not moved to attached.");
return new AsyncReply(resource);
}
var reply = new AsyncReply();
this.#resourceRequests.set(id, reply);
var newSequence =
requestSequence != null ? [...requestSequence, id] : [id];
var self = this;
this.#sendRequest(IIPPacketAction.AttachResource)
.addUint32(id)
.done()
.then(function (rt) {
if (rt == null) {
reply.triggerError(new AsyncException(ErrorType.Management,
ExceptionCode.ResourceNotFound, "Null response"));
return;
}
var dr;
let classId = rt[0];
let template = null;
if (resource == null)
{
template = Warehouse.getTemplateByClassId(classId, TemplateType.Resource);
if (template?.definedType != null && template?.isWrapper)
dr = new template.definedType(self, id, rt[1], rt[2]);
else
dr = new DistributedResource(self, id, rt[1], rt[2]);
}
else {
dr = resource;
template = resource.instance.template;
}
//let dr = resource || new DistributedResource(self, id, rt[1], rt[2]);
let transmissionType = rt[3] ;
let content = rt[4] ;
let initResource = (ok) => {
Codec.parse(content, 0, self, newSequence, transmissionType)
.reply
.then((ar) => {
var pvs = new PropertyValueArray();
for (var i = 0; i < ar.length; i += 3)
pvs.push(new PropertyValue(
ar[i + 2], ar[i], ar[i + 1]));
dr._attach(pvs);
self.#resourceRequests.remove(id);
// move from needed to attached
self.#neededResources.remove(id);
self.#attachedResources.set(id, new WeakRef(dr));
reply.trigger(dr);
})
.error((ex) => reply.triggerError(ex));
};
if (template == null)
{
self.getTemplate(rt[0]).then(function (tmp) {
// ClassId, ResourceAge, ResourceLink, Content
if (resource == null) {
Warehouse.put(id.toString(), dr, self, null, tmp).then(function(ok){
initResource(ok);
}).error(function(ex){
reply.triggerError(ex);
});
}
else
{
initResource(ok);
}
}).error(function(ex){
reply.triggerError(ex);
});
} else {
if (resource == null) {
Warehouse.put(id.toString(), dr, self, null, template)
.then(initResource)
.error((ex) => reply.triggerError(ex));
} else {
initResource(resource);
}
}
}).error(function(ex){
reply.triggerError(ex);
});
return reply;
}
getRecord(resource, fromDate, toDate) {
if (resource instanceof DistributedResource) {
if (resource._p.connection != this)
return new AsyncReply(null);
var reply = new AsyncReply();
var self = this;
this.#sendRequest(IIPPacketAction.ResourceHistory)
.addUint32(resource._p.instanceId)
.addDateTime(fromDate).addDateTime(toDate)
.done()
.then(function (rt) {
Codec.historyParser(rt[0], 0, rt[0].length, resource, self, null).then(function (history) {
reply.trigger(history);
});
});
return reply;
}
else
return new AsyncReply(null);
}
#instance_resourceDestroyed = function(resource) {
this.#unsubscribe(resource);
// compose the packet
this.#sendEvent(IIPPacketEvent.ResourceDestroyed)
.addUint32(resource.instance.id)
.done();
}
#instance_propertyModified = function(info) {
this.#sendEvent(IIPPacketEvent.PropertyUpdated)
.addUint32(info.resource.instance?.id)
.addUint8(info.propertyTemplate.index)
.addUint8Array(Codec.compose(info.value, this))
.done();
}
#instance_eventOccurred = function(info) {
if (info.eventTemplate.listenable)
{
// check the client requested listen
if (!this.#subscriptions.has(resource))
return;
if (!this.#subscriptions.get(resource).includes(et.index))
return;
}
if (info.receivers instanceof Function)
if (!info.receivers(this.sessions))
return;
if (info.resource.instance.applicable(this.session,
ActionType.ReceiveEvent, info.eventTemplate, info.issuer) == Ruling.Denied)
return;
// compose the packet
this.#sendEvent(IIPPacketEvent.EventOccurred)
.addUint32(info.resource.instance.id)
.addUint8(info.eventTemplate.index)
.addUint8Array(Codec.compose(info.value, this))
.done();
}
IIPRequestAddChild(callback, parentId, childId) {
var self = this;
Warehouse.getById(parentId).then(function (parent) {
if (parent == null) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceNotFound);
return;
}
Warehouse.getById(childId).then(function (child) {
if (child == null) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceNotFound);
return;
}
if (parent.instance.applicable(self.#session, ActionType.AddChild, null) != Ruling.Allowed) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.AddChildDenied);
return;
}
if (child.instance.applicable(self.#session, ActionType.AddParent, null) != Ruling.Allowed) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.AddParentDenied);
return;
}
parent.instance.children.add(child);
self.#sendReply(IIPPacketAction.AddChild, callback)
.done();
//child.Instance.Parents
});
});
}
IIPRequestRemoveChild(callback, parentId, childId) {
var self = this;
Warehouse.getById(parentId).then(function (parent) {
if (parent == null) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceNotFound);
return;
}
Warehouse.getById(childId).then(function (child) {
if (child == null) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceNotFound);
return;
}
if (parent.instance.applicable(self.#session, ActionType.RemoveChild, null) != Ruling.Allowed) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.AddChildDenied);
return;
}
if (child.instance.applicable(self.#session, ActionType.RemoveParent, null) != Ruling.Allowed) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.AddParentDenied);
return;
}
parent.instance.children.remove(child);
self.#sendReply(IIPPacketAction.RemoveChild, callback)
.done();
//child.Instance.Parents
});
});
}
IIPRequestRenameResource(callback, resourceId, name) {
var self = this;
Warehouse.getById(resourceId).then(function (resource) {
if (resource == null) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceNotFound);
return;
}
if (resource.instance.applicable(self.#session, ActionType.Rename, null) != Ruling.Allowed) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.RenameDenied);
return;
}
resource.instance.name = name;
self.#sendReply(IIPPacketAction.RenameResource, callback)
.done();
});
}
IIPRequestResourceChildren(callback, resourceId) {
var self = this;
Warehouse.getById(resourceId).then(function (resource) {
if (resource == null) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceNotFound);
return;
}
self.#sendReply(IIPPacketAction.ResourceChildren, callback)
.addUint8Array(Codec.compose(resource.instance.children.toArray(), self))
.done();
});
}
IIPRequestResourceParents(callback, resourceId) {
var self = this;
Warehouse.getById(resourceId).then(function (resource) {
if (resource == null) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceNotFound);
return;
}
self.#sendReply(IIPPacketAction.ResourceParents, callback)
.addUint8Array(Codec.compose(resource.instance.parents.toArray(), self))
.done();
});
}
IIPRequestClearAttributes(callback, resourceId, attributes, all = false) {
let self = this;
Warehouse.getById(resourceId).then(function (r) {
if (r == null) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceNotFound);
return;
}
if (r.instance.store.instance.applicable(self.#session, ActionType.UpdateAttributes, null) != Ruling.Allowed) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.UpdateAttributeDenied);
return;
}
var attrs = [];
if (!all)
attrs = attributes.getStringArray(0, attributes.length);
if (r.instance.removeAttributes(attrs))
self.#sendReply(all ? IIPPacketAction.ClearAllAttributes : IIPPacketAction.ClearAttributes, callback)
.done();
else
self.#sendError(AsyncReply.ErrorType.Management, callback, ExceptionCode.UpdateAttributeFailed);
});
}
IIPRequestUpdateAttributes(callback, resourceId, attributes, clearAttributes = false) {
var self = this;
Warehouse.getById(resourceId).then(function (r) {
if (r == null) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.ResourceNotFound);
return;
}
if (r.instance.store.instance.applicable(self.#session, ActionType.UpdateAttributes, null) != Ruling.Allowed) {
self.#sendError(ErrorType.Management, callback, ExceptionCode.UpdateAttributeDenied);
return;
}
DataDeserializer.typedListParser(attributes, 0, attributes.length, this, null)
.then(function (attrs) {
if (r.instance.setAttributes(attrs, clearAttributes))
self.#sendReply(clearAttributes ? IIPPacketAction.ClearAllAttributes : IIPPacketAction.ClearAttributes,
callback)
.done();
else
self.#sendError(ErrorType.Management, callback, ExceptionCode.UpdateAttributeFailed);
});
});
}
getChildren(resource) {
if (resource._p.connection != this)
return new AsyncReply(null);
var rt = new AsyncReply();
var self = this;
this.#sendRequest(IIPPacketAction.ResourceChildren)
.addUint32(resource._p.instanceId)
.done()
.then(function (ar) {
let dataType = ar[0];
let data = ar[1];
Codec.parse(data, 0, self, null, dataType).reply.then((resources) => {
rt.trigger(resources);
})
.error((ex) => rt.triggerError(ex));
});
return rt;
}
getParents(resource) {
if (resource._p.connection != this)
return new AsyncReply(null);
var rt = new AsyncReply();
var self = this;
this.#sendRequest(IIPPacketAction.ResourceParents)
.addUint32(resource._p.instanceId)
.done()
.then(function (ar) {
let dataType = ar[0] ;
let data = ar[1];
Codec.parse(data, 0, self, null, dataType).reply.then((resources) => {
rt.trigger(resources);
})
.error((ex) => rt.triggerError(ex));
});
return rt;
}
removeAttributes(resource, attributes = null) {
if (resource._p.connection != this)
return new AsyncReply(null);
var rt = new AsyncReply();
if (attributes == null)
this.#sendRequest(IIPPacketAction.ClearAllAttributes)
.addUint32(resource._p.instanceId)
.done()
.then(function (ar) {
rt.trigger(true);
}).error(function (ex) { rt.triggerError(ex); });
else {
var attrs = DC.stringArrayToBytes(attributes);
this.#sendRequest(IIPPacketAction.ClearAttributes)
.addUint32(resource.instance.id)
.addUint32(attrs.length)
.addUint8Array(attrs)
.done()
.then(function (ar) {
rt.trigger(true);
}).error(function (ex) { rt.triggerError(ex); });
}
return rt;
}
setAttributes(resource, attributes, clearAttributes = false) {
if (resource._p.connection != this)
return new AsyncReply(null);
var rt = new AsyncReply();
this.#sendRequest(clearAttributes ? IIPPacketAction.UpdateAllAttributes : IIPPacketAction.UpdateAttributes)
.addUint32(resource._p.instanceId)
.addUint8Array(Codec.compose(attributes, this))
.done()
.then(function () {
rt.trigger(true);
}).error(function (ex) { rt.triggerError(ex); });
return rt;
}
getAttributes(resource, attributes = null) {
if (resource._p.connection != this)
return new AsyncReply(null);
let rt = new AsyncReply();
let self = this;
if (attributes == null) {
this.#sendRequest(IIPPacketAction.GetAllAttributes)
.addUint32(resource._p.instanceId)
.done()
.then(function (ar) {
let dataType = ar[0];
let data = ar[1];
Codec.parse(data, 0, self, null, dataType).reply.then((st) => {
resource.instance?.setAttributes(st);
rt.trigger(st);
})
.error((ex) => rt.triggerError(ex));
}).error(function(ex) { rt.triggerError(ex); });
}
else {
let attrs = DC.stringArrayToBytes(attributes);
this.#sendRequest(IIPPacketAction.GetAttributes)
.addUint32(resource._p.instanceId)
.addUint32(attrs.length)
.addUint8Array(attrs)
.done()
.then(function (ar) {
let dataType = ar[0] ;
let data = ar[1] ;
Codec.parse(data, 0, self, null, dataType).reply
.then((st) => {
resource.instance?.setAttributes(st);
rt.trigger(st);
})
.error((ex) => rt.triggerError(ex));
}).error((ex) => rt.triggerError(ex));;
}
return rt;
}
#keepAliveTimerElapsed()
{
// @TODO: port this
// if (!this.isConnected)
// return;
let self = this;
let now = new Date();
let interval = this.#lastKeepAliveSent == null ? 0 :
(now - this.#lastKeepAliveSent);
this.#lastKeepAliveSent = now;
this.#sendRequest(IIPPacketAction.KeepAlive)
.addDateTime(now)
.addUint32(interval)
.done()
.then(x =>
{
self.#jitter = x[1];
self.#keepAliveTimer = setTimeout(() => self.#keepAliveTimerElapsed(), self.keepAliveInterval * 1000);
//console.log("Keep Alive Received " + self.jitter);
// run GC
let toBeRemoved =[];
for(let i = 0; i < self.#attachedResources.length; i++){
let r = self.#attachedResources.values[i].deref();
if (r == null) {
let id = self.#attachedResources.keys[i];
// send detach
self._sendDetachRequest(id);
toBeRemoved.push(id);
}
}
if (toBeRemoved.length > 0)
console.log("GC: " + toBeRemoved.length);
for(let id of toBeRemoved)
self.#attachedResources.remove(id);
}).error((ex) =>
{
console.log(ex);
self.close();
}).timeout(self.keepAliveTime * 1000);
//console.log("Keep alive sent ");
}
staticCall(classId, index, parameters)
{
var pb = Codec.compose(parameters, this);
var reply = new AsyncReply();
var c = this.#callbackCounter++;
this.#requests.add(c, reply);
this.#sendParams()
.addUint8(0x40 | IIPPacketAction.StaticCall)
.addUint32(c)
.addGuid(classId)
.addUint8(index)
.addUint8Array(pb)
.done();
return reply;
}
call(procedureCall)
{
var args = Map.from(UInt8, Object);
for (var i = 0; i < arguments.Length - 2; i++)
args.add(i, arguments[i+1]);
return this.callArgs(procedureCall, args);
}
callArgs(procedureCall, parameters)
{
var pb = Codec.Compose(parameters, this);
var reply = new AsyncReply();
var c = this.#callbackCounter++;
this.#requests.add(c, reply);
var callName = DC.stringToBytes(procedureCall);
sendParams()
.addUint8(0x40 | IIPPacketAction.ProcedureCall)
.addUint32(c)
.addUint16(callName.length)
.addUint8Array(callName)
.addUint8Array(pb)
.done();
return reply;
}
IIPRequestKeepAlive(callbackId, peerTime, interval)
{
let jitter = 0;
let now = new Date();
if (this.#lastKeepAliveReceived != null)
{
var diff = now - this.#lastKeepAliveReceived;
//Console.WriteLine("Diff " + diff + " " + interval);
jitter = Math.abs(diff - interval);
}
this.#sendParams()
.addUint8(0x80 | IIPPacketAction.KeepAlive)
.addUint32(callbackId)
.addDateTime(now)
.addUint32(jitter)
.done();
this.#lastKeepAliveReceived = now;
}
static get template() {
return new TemplateDescriber("Esiur", [new Prop("status", UInt8)]);
}
}