Gathering detailed insights and metrics for node-pocket-safe
Gathering detailed insights and metrics for node-pocket-safe
Gathering detailed insights and metrics for node-pocket-safe
Gathering detailed insights and metrics for node-pocket-safe
npm install node-pocket-safe
Typescript
Module System
Node Version
NPM Version
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
Pocket-Safe é uma proposta inovadora para um sistema descentralizado que permite o armazenamento e gerenciamento offline de blockchain, com foco em escalabilidade, segurança e acessibilidade. Inspirado em tecnologias modernas, ele introduz o conceito de um agente fiscal que verifica periodicamente a integridade dos blockchains por meio de assinaturas digitais, eliminando a necessidade de armazenar todas as informações em um servidor central.
Este modelo permite transações offline usando métodos de comunicação local, como Bluetooth, Wi-Fi ou NFC, mantendo a segurança e a confiabilidade das transações. Para operações de longa distância, o agente fiscal atua como um intermediário apenas para validar e sincronizar informações.
Ao adotar o conceito Pocket-Safe, esta API fornece ferramentas que aderem aos mesmos princípios de descentralização e escalabilidade, permitindo o gerenciamento confiável de dados com suporte para validação periódica e foco em um modelo baseado em confiança entre os participantes. É ideal para aplicações que exigem operações seguras e descentralizadas.
Para instalar a API Pocket-Safe, você pode usar no npm ou yarn:
1npm install node-pocket-safe
ou
1yarn add node-pocket-safe
Para começar a usar a API Pocket-Safe, você precisa importar o módulo e criar uma instância do PocketSafe, neste momente, terá que escolher qual personagem atuará, se será um agente fiscal ou um participante. Ouse seja, Server ou Client.
Mas, antes de qualquer coisa, ambas instâncias, exigem uma personalização de propriedades, essas propriedades servem para gerenciar os dados, adaptando-se a sua escolha de armazenamento.
Criando uma instância do Server como agente fiscal:
1import { Server, ServerSettings, AddressInfo, CurrentSync, VerificationBlock, PendingShipment } from 'node-pocket-safe'; 2 3const key = process.env.KEY as string; // Chave de acesso 4const password = process.env.PASSWORD as string; // Senha de acesso 5 6class CustomServer<D = any> extends Server<D> { 7 private readonly addressList: Map<string, AddressInfo> = new Map(); 8 private readonly currentSync: Map<string, CurrentSync<D>> = new Map(); 9 private readonly verificationBlock: Map<string, VerificationBlock[]> = new Map(); 10 private readonly pendingShipment: Map<string, PendingShipment[]> = new Map(); 11 12 constructor(settings: Partial<ServerSettings<D>>) { 13 super(settings); 14 } 15 16 async validatePayload(payload: D): Promise<boolean> { 17 return Promise.resolve(true); 18 } 19 20 async hasAddress(address: string): Promise<boolean> { 21 return this.addressList.has(address); 22 } 23 24 async getAddress(address: string): Promise<AddressInfo | undefined> { 25 return this.addressList.get(address); 26 } 27 28 async pushAddress(address: string, data: AddressInfo): Promise<void> { 29 this.addressList.set(address, data); 30 } 31 32 async updateAddress(address: string, data: AddressInfo): Promise<void> { 33 this.addressList.set(address, data); 34 } 35 36 async setCurrentSync(address: string, current: CurrentSync<D>): Promise<void> { 37 this.currentSync.set(address, current); 38 } 39 40 async getCurrentSync(address: string): Promise<CurrentSync<D> | undefined> { 41 return this.currentSync.get(address); 42 } 43 44 async removeCurrentSync(address: string): Promise<void> { 45 this.currentSync.delete(address); 46 } 47 48 async getBlocksVerifications(address: string): Promise<VerificationBlock[]> { 49 return this.verificationBlock.get(address) ?? []; 50 } 51 52 async pushBlockVerification(address: string, data: VerificationBlock): Promise<void> { 53 this.verificationBlock.set(address, [...(this.verificationBlock.get(address) ?? []), data]); 54 } 55 56 async getBlockVerification(address: string, sequence: number): Promise<VerificationBlock | undefined> { 57 return (this.verificationBlock.get(address) ?? []).find((block) => block.sequence === sequence); 58 } 59 60 async removeBlockVerification(address: string, sequence: number): Promise<void> { 61 this.verificationBlock.set( 62 address, 63 (this.verificationBlock.get(address) ?? []).filter((block) => block.sequence !== sequence), 64 ); 65 } 66 67 async getPendingShipments(address: string): Promise<PendingShipment[]> { 68 return this.pendingShipment.get(address) ?? []; 69 } 70 71 async pushPendingShipment(address: string, data: PendingShipment): Promise<void> { 72 this.pendingShipment.set(address, [...(this.pendingShipment.get(address) ?? []), data]); 73 } 74 75 async getPendingShipment(address: string, hash: string): Promise<PendingShipment | undefined> { 76 return (this.pendingShipment.get(address) ?? []).find((block) => block.hash === hash); 77 } 78 79 async removePendingShipment(address: string, hash: string): Promise<void> { 80 this.pendingShipment.set( 81 address, 82 (this.pendingShipment.get(address) ?? []).filter((block) => block.hash !== hash), 83 ); 84 } 85} 86 87const pocketSafeServer = new CustomServer({ 88 key, 89 password, 90 envInfo: { 91 name: "demo", 92 api: "http://localhost:3000", 93 version: "0.0.1", 94 }, 95});
Criando uma instância do Client como participante:
1import { Block, Client, ClientInfo, ClientSettings } from 'node-pocket-safe'; 2 3const password = process.env.PASSWORD as string; 4 5class CustomClient<D = any> extends Client<D> { 6 private readonly clients: Set<ClientInfo> = new Set(); 7 private readonly blocks: Map<string, Block[]> = new Map(); 8 9 constructor(settings: Partial<ClientSettings>) { 10 super(settings); 11 } 12 13 async getClients(): Promise<ClientInfo[]> { 14 return this.clients.entries(); 15 } 16 17 async pushClient(info: ClientInfo): Promise<void> { 18 this.clients.add(info); 19 } 20 21 async getBlocks(publicKey: string): Promise<Block[]> { 22 return this.blocks.get(publicKey) ?? []; 23 } 24 25 async pushBlock(publicKey: string, block: Block): Promise<void> { 26 this.blocks.set(publicKey, [...(this.blocks.get(publicKey) ?? []), block]); 27 } 28} 29 30const pocketSafeClient = new CustomClient({ password }); 31 32(async ()=>{ 33 const [ blockchain ] = await pocketSafeClient.listClients(); // Lista todos os clientes 34 35 const password:string = "..."; // Senha de acesso 36 pocketSafeClient.open(blockchain.address, password); // Abre um cliente 37})();
Como já vimos antes, temos dois personagens, Server e Client. Ambos possuem métodos e propriedades específicas, ao longo da documentação, você poderá ver todas as informações necessárias para usar a API Pocket-Safe.
A primeira coisa que você precisa fazer, é ter um Client para poder interagir com o Server ou outros Clients. Nesse caso, temos duas alternativas, criar um novo Client ou abrir um Client existente. Para criar um novo Client, você pode usar o método create
da instância do Client, mas, para isso, antes é preciso ter um código ENV do servidor, ou seja, do agente fiscal.
1const env = pocketSafeServer.getEnvCode(); // Obtem o código ENV do servidor 2 3const client = await pocketSafeClient.create(env);
Mas ainda não acabou, o método create
retorna uma promessa, que, quando resolvida, retorna um objeto com address
(endereço do Client), timestamp
(número em milissegundos indicando a data do momento de criação), prepareGenesisBlock
(método para preparar o bloco genesis) e finish
(método para finalizar a criação do Client).
Ou seja, a partir desse momento, teremos que obter algumas informações do servidor para proceguirmos com a criação do cliente. O servidor exifirá do Client o address
para retornar ao cliente os dados que necessita para a criação do bloco genesis. Serão elas, timestamp
, data
, sign
, hash
e nonce
:
1// Obter os dados do servidor. Função fictícia.
2const data = await fetchServer("createGenesisBy", client.address);
3
4await client.prepareGenesisBlock(data.timestamp, data.data, ddata.sign, data.hash, data.nonce);
Após realizar a etapa de preparação do bloco genesis, a criação do Client estará pronto para ser finalizado, ou seja, para ser adicionado na base de dados previamente definitos. Para isso, basta chamar o método finish
:
1await client.finish();
Observe que, o método
finish
, só poderá ser útil, se o bloco genesis for preparado corretamente. Caso contrário, ao chamar o métodofinish
antes de preparar o bloco genesis, emitirá um erro, informando que o bloco genesis não foi preparado.
Para criar um novo Server, você precisará apenas de criar a chave protegida por senha, que será usada para obter a chave pública e privada do servidor. Para isso, basta chamar o método estático createKey
da instância do Server:
1import { Server } from 'node-pocket-safe'; 2 3const password:string = "..."; // Senha de acesso 4const key:string = Server.createKey(password);
Será preciso armazenar a chave em um local seguro, pois, sem ela, não será possível acessar o servidor. Além disso, a chave será usada para criar a instância do Server:
1const pocketSafeServer = new CustomServer({
2 key,
3 password,
4 envInfo: {
5 name: "demo",
6 api: "http://localhost:3000",
7 version: "0.0.1",
8 },
9});
O Client é o participante de uma rede, ou seja, no conceito Pocket-Safe, ele é a blockchain. O Client é responsável por realizar transações, receber transações, verificar a confiabilidade da blockchain, sincronizar os blocos com o servidor para prestação de contas e entre outras funções. Para realizar essas funções, o Client fornece métodos e propriedades específicas.
Um remetente é um cliente que deseja realizar uma transação com outro cliente, ou seja, o beneficiário. Para isso, basta usar a propriedade createSender
da instância do Client com as informações de payload, e enviar para o beneficiário o data
gerado. Seus parâmetros são o payload
da transação e uma callback que será chamada em cada estágio, essa callback recebe uma string em base64 que deve ser enviado para o beneficiário, isso se o beneficiário estiver usando o método createBeneficiary
. O método createSender
retorna uma promessa que, quando resolvida, retorna uma função que deverá ser chamada a cada resposta do beneficiário:
1const payload:any = {...}; // Dados da transação 2 3const receiveFromBeneficiary = await sendingClient.createSender(payload, (data) => { 4 receiveFromSender(data); 5});
Para realizar uma transação entre remetente e beneficiário a distância, será necessário usar o servidor como intermediário. Nesse caso, temos o método parecido como o createSender
, que é o createSenderForAddress
. Ele cria as etapas necessárias para a realizar a transação entre remetente e beneficiário a distância usando o servidor como intermediário. Seus parâmetros são o address
do beneficiário, o payload
da transação e uma callback que será chamada em cada estágio, essa callback recebe uma string em base64 que deve ser enviado para o servidor. O método createSenderForAddress
retorna uma promessa que, quando resolvida, retorna uma função que deverá ser chamada a cada resposta do servidor:
1const address:string = "..."; // Endereço do beneficiário 2const payload:any = {...}; // Dados da transação 3 4const receiveFromServer = await pocketSafeClient.createSenderForAddress(address, payload, (data) => { 5 // Função fictícia 6 receiveFromSender(data); 7});
Um beneficiário é um cliente que deseja receber uma transação de outro cliente, ou seja, o remetente. Para isso, usar o método createBeneficiary
para simplificar o processo de transação entre remetente e beneficiário. Requer apenas um parâmetro, que é uma callback que será chamada em cada estágio, essa callback recebe uma string em base64 que deve ser enviado para o remetente. O método createBeneficiary
retorna uma promessa que, quando resolvida, retorna uma função que deverá ser chamada a cada resposta do remetente:
1const receiveFromSender = await pocketSafeClient.createBeneficiary((data) => { 2 receiveFromBeneficiary(data); 3});
É importante ressaltar que, estamos apenas vendo como usar os métodos da instância do Client. Para realizar o trâmite de comunicação entre remetente e beneficiário, será necessário usar métodos de comunicação local, como Bluetooth, Wi-Fi ou NFC. Isso se falando de dispositivos móveis diferentes, mas, se falando de dispositivos desktop, poderá ser usado métodos de comunicação local, como WebSocket. A escolha do método de comunicação dependerá do desenvolvedor. A proposta dessa API é apenas fornecer as ferramentas necessárias para realizar o conceito Pocket-Safe.
Para receber uma transação do remetente a distância, será necessário usar o servidor como intermediário. Nesse caso, temos o método parecido como o createBeneficiary
, que é o checkReceiptPending
. Ele cria as etapas necessárias para a receber as transações pendentes. Ele requer apenas um parâmetro, que é uma callback que será chamada em cada estágio, essa callback recebe uma string em base64 que deve ser enviado para o servidor. O método checkReceiptPending
retorna uma promessa que, quando resolvida, retorna uma função que deverá ser chamada a cada resposta do servidor:
1const receiveFromServer = await pocketSafeClient.checkReceiptPending((data) => { 2 // Função fictícia 3 receiveFromBeneficiary(data); 4});
Se caso não houver transações pendentes, o método checkReceiptPending
retornará um erro informando que não há transações pendentes.
Observe que, o método checkReceiptPending
opera uma transação pendentente por vez. Se houver mais de uma transação pendente, será necessário chamar o método checkReceiptPending
novamente para verificar a próxima transação pendente.
A confiabilidade é um dos pontos mais importantes da API Pocket-Safe. Para garantir a confiabilidade, a API Pocket-Safe fornece um método para verificar a integridade da blockchain por através de assinaturas digitais, em especial, a assinatura digital do agente fiscal. A assinatura digital do agente fiscal é obtido após a sincronização dos blocos entre servidor e cliente, o tal chamado de prestação de contas. Isso é fundamental na hora de realizar transações, pois, se a assinatura digital do agente fiscal não estiver conforme o esperado, a transação não será realizada, sendo possível rejeição por um dos participantes usando os métodos createSender
e createBeneficiary
. Para estabelecer a taxa mínima de confiabilidade, basta definir no método reliability
da instância do Client, seu valor deverá ser entre 0 e 1, sendo que, quando mais perto de 1, mais confiável deverá ser a blockchain:
1const pocketSafeClient = new CustomClient({
2 password,
3 reliability: 0.9 // Confiabilidade de 90%
4});
A verificação da confiabilidade é feita automaticamente pela API Pocket-Safe com a utilização dos métodos createSender
e createBeneficiary
. Durante a transação, é feito o cálculo da confiabilidade em base na ultima data em que o agente fiscal sincronizou com o cliente, com sua assinatura atualizada para confirmação da integridade dos blocos. Se a confiabilidade for menor que o valor definido, a transação poderá ser rejeitada. Em casos espesíficos, se, a confiabilidade for assima de 0.70 e abaixo do valor definido, o usuário poderá ser notificado para que ele possa decidir se pretende realizar/receber a transação ou não, se a confiabilidade for abaixo de 0.70, a transação será rejeitada automaticamente se a confiabilidade for abaixo do valor definido.
Nota: A confiabilidade é calculada em base na assinatura digital do agente fiscal e na data da última sincronização entre servidor e cliente.
Para saber a confiabilidade da blockchain, basta chamar o método getReliability
da instância do Client. O método getReliability
retornará um número entre 0 e 1, sendo que, quanto mais perto de 1, mais confiável seria a blockchain:
1const reliability:number = pocketSafeClient.getReliability();
Para evitar problemas durante as transações, é preciso que a blockchain esteja em dia com o servidor, ou seja, é crusial que preste contas com o servidor. Para isso, a API Pocket-Safe fornece um método para sincronizar os blocos entre as partes. Essa parte é fundamenta para garantir a integridade dos blocos e a confiabilidade das transações. A integridade dos blocos é medita com base na assinatura digital do agente fiscal, que é responsável em validar cada bloco. Se um bloco se quer estiver fora de ordem ou que possa levantar possíveis suspeitas, o servidor poderá se recusar a atualizar sua assinatura digital, o que poderá levar a baixa confiabilidade da blockchain, assim, essa blockchain se torna comprometida na rede e imcapaz de receber ou enviar novas operações. Ou seja, sem a assinatura digital do agente fiscal atualizada, as transações podem não ser realizadas conforme o nível de confiabilidade esperado.
Para sincronizar os blocos, temos muitos métodos importantes para serem usados entre servidor e cliente. Do lado do cliente, temos o método syncBlockchain
da instância do Client, que iniciará o processo de sincronização, percorrendo cada bloco da blockchain e enviando ao servidor:
1// Um listener para receber os blocos de sincronização e enviar ao servidor
2pocketSafeClient.on('chunkBlock', (address, block, index, length, confirm) => {
3 console.log(`Syncing block ${index + 1}/${length}`);
4 // Implementação
5 // Função fictícia
6 fetchServer("syncBlockchain", address, block).then(confirm);
7});
8
9pocketSafeClient.syncBlockchain() // Inicia a sincronização
10.then(({ address })=>{
11 fetchServer("getSignatureBy", address) // Obtem a assinatura do servidor
12 .then((signature) => {
13 pocketSafeClient.updateServerSignature(signature); // Atualiza a assinatura do servidor
14 });
15});
Para assegurar a integridade das blockchains no conceito Pocket-Safe, seria necessário introduzir um agente fiscal, que, na prática, seria representado por um servidor denominado "rede". Essa rede desempenharia funções cruciais, como gerar os blocos gênese, verificar a integridade dos blocos da blockchain durante a etapa de sincronização, atualizar sua assinatura gênese para validar a consistência da cadeia, além de atuar como intermediário no envio e recebimento de blocos entre remetentes e beneficiários a longa distância.
Para criar um bloco gênese, basta chamar o método createGenesisBy
da instância do Server. O método createGenesisBy
requer um parâmetro address
do cliente:
1const address:string = "..."; // Endereço público do cliente 2 3const genesisBlock = await pocketSafeServer.createGenesisBy(address);
O método createGenesisBy
retornará um objeto com os dados do bloco gênese, como timestamp
, payload
, sign
, hash
e nonce
. Esses dados serão usados pelo cliente para preparar o bloco gênese. Ou seja, sua função é receber do cliente o seu endereço público, gerar as informações que o cliente precisa para preparar o bloco gênese e enviar para o cliente.
Para sincronizar os blocos, temos muitos métodos importantes para serem usados entre servidor e cliente. Do lado do servidor, temos o método syncBlockchain
da instância do Server, que bloco por bloco da blockchain recebido do cliente e verifica a integridade de cada bloco:
1// Exemplo por parte do servidor
2const syncBlock = async (address, block) => {
3 await pocketSafeServer.syncBlockchain(address, block); // Verifica a integridade do bloco
4};
5
6// Exemplo por parte do cliente
7pocketSafeClient.on('chunkBlock', (address, block, index, length, confirm) => {
8 // Implementação
9 syncBlock(address, block) // Envia o bloco para o servidor
10 .then(confirm); // Se não houver erros, confirma o bloco
11});
Como vemos nesse exemplo, o cliente envia os blocos para o servidor e o servidor verifica a integridade de um bloco específico por vez. Se um bloco estiver fora de ordem ou que possa levantar suspeitas, o servidor emite um erro informando que o bloco não é válido e o processo de sincronização é interrompido, ou seja, o servidor se recusa a atualizar sua assinatura digital, o que poderá levar a baixa confiabilidade da blockchain.
No exemplo, como que o código por parte do servidor e cliente estão num mesmo arquivo de código. O ideal seria usar métodos de comunicação web, como WebSocket, para realizar a sincronização entre servidor e cliente. O servidor poderá enviar os blocos para o cliente e o cliente poderá enviar os blocos para o servidor. A escolha do método de comunicação dependerá do desenvolvedor. A proposta dessa API é apenas fornecer as ferramentas necessárias para realizar o conceito Pocket-Safe.
Para atualizar a assinatura do servidor após realizar o processo de sincronização e todos os blocos estiverem corretos, basta chamar o método getSignatureBy
da instância do Server. O método getSignatureBy
requer um parâmetro address
do cliente:
1// Exemplo por parte do servidor 2const getSignature = async (address) => { 3 return await pocketSafeServer.getSignatureBy(address); // Gera a assinatura atualizada do servidor 4}; 5 6// Exemplo por parte do cliente 7pocketSafeClient.syncBlockchain() // Inicia a sincronização 8.then(({ address })=>{ 9 getSignature(address) // Obtem a assinatura do servidor 10 .then((signature) => { 11 pocketSafeClient.updateServerSignature(signature); // Atualiza a assinatura do servidor 12 }); 13});
O método getSignatureBy
retornará um objeto com os dados da assinatura do servidor que serão usados pelo cliente para atualizar a assinatura do servidor. Ou seja, sua função é receber do cliente o endereço público, gerar a assinatura atualizada do servidor e enviar para o cliente. Poderá resultar em erros em caso de falha na sincronização dos blocos ou se não houver executado a sincronização dos blocos antes de chamar o método getSignatureBy
.
Para realizar uma transação entre remetente e beneficiário a longa distância, será necessário usar o servidor como intermediário. Nesse caso, temos o método parecido como o createSenderForAddress
, que é o sendToAddress
da instância do Server. Ele cria as etapas necessárias para a realizar a transação entre remetente e beneficiário a distância usando o servidor como intermediário. O método requer apenas um parâmetro, que é data
, uma string em base64 que recebe do remetente por através de método createSenderForAddress
. O método sendToAddress
retorna uma promessa que, quando resolvida, retorná uma string em base64 que deverá ser enviada para o beneficiário:
1// Exemplo por parte do servidor 2const receiveFromSender = async (data) => { 3 return await pocketSafeServer.sendToAddress(data); // Recebe o data do remetente, processa e retorna um data para o remetente 4}; 5 6// Exemplo por parte do cliente 7const address:string = "..."; // Endereço do beneficiário 8const payload:any = {...}; // Dados da transação 9 10const receiveFromServer = await pocketSafeClient.createSenderForAddress(address, payload, (data) => { // Cria o sender para o servidor 11 receiveFromSender(data) // Envia o data para o servidor 12 .then(receiveFromServer); // Recebe o data do servidor 13});
Caso a transação pendente seja realizado, o servidor dispara um evento listener pendingReceipt
, que poderá ser usado para notificar o beneficiário que há transação pendente. O beneficiário poderá usar o método receiveToAddress
para verificar e receber a transação pendente e, se estiver tudo certo, poderá incluir o bloco na sua blockchain.
1// Exemplo por parte do cliente 2const receivePending = await ()=>{ // Função para receber a transação pendente 3 // Implementação 4} 5 6// Exemplo por parte do servidor 7pocketSafeServer.on('pendingReceipt', (fromAddress) => { 8 // Implementação 9 if (fromAddress === pocketSafeClient.address) { // Verifica se a transação é do beneficiário 10 receivePending(); // Recebe a transação pendente 11 } 12});
Para receber uma transação do remetente a distância, será necessário usar o servidor como intermediário. Nesse caso, temos o método parecido como o sendToAddress
, que é o receiveToAddress
da instância do Server. Ele tem a mesma função que o sendToAddress
, mas, para em caso de transação pendente. O método requer apenas um parâmetro, que é data
, uma string em base64 que recebe do beneficiário por através de método checkReceiptPending
. O método receiveToAddress
retorna uma promessa que, quando resolvida, retorná uma string em base64 que deverá ser enviada para o beneficiário:
1// Exemplo por parte do servidor 2const receiveFromBeneficiary = async (data) => { 3 return await pocketSafeServer.receiveToAddress(data); // Recebe o data do beneficiário, processa e retorna um data para o beneficiário 4}; 5 6// Exemplo por parte do cliente 7const receiveFromServer = await pocketSafeClient.checkReceiptPending((data) => { // Verifica a transação pendente e o recebe 8 receiveFromBeneficiary(data) // Envia o data para o servidor 9 .then(receiveFromServer); // Recebe o data do servidor 10});
Haverá casos em que o servidor possa querer enviar uma transação pendente para o beneficiário, fazendo o papel de remetente. Para isso, basta chamar o método createSubmission
da instância do Server. O método createSubmission
requer os parâmetros address
do beneficiário e payload
da transação:
1const address:string = "..."; // Endereço do beneficiário 2const payload:any = {...}; // Dados da transação 3 4await pocketSafeServer.createSubmission(address, payload);
Haverá casos em que o servidor possa querer solicitar uma cobrança de um cliente, fazendo o papel de beneficiário. Para isso, basta chamar o método requestCharge
da instância do Server. O método requestCharge
requer os parâmetros address
do cliente e payload
da transação:
1const address:string = "..."; // Endereço do cliente 2const payload:any = {...}; // Dados da transação 3 4await pocketSafeServer.requestCharge(address, payload);
No vulnerabilities found.
No security vulnerabilities found.