Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dca0a77b0d | |||
| 6e314bde56 | |||
| 47b3b3614b | |||
| 5da6a768b0 | |||
| f03aef1954 | |||
| adebc51630 | |||
| 958de927aa | |||
| 037afc350c | |||
| 10b5d6be57 |
@@ -2,5 +2,6 @@
|
||||
.gitignore
|
||||
.npmignore
|
||||
.travis.yml
|
||||
examples
|
||||
node_modules
|
||||
spec
|
||||
|
||||
+2
-1
@@ -4,7 +4,8 @@ node_js:
|
||||
- "0.6"
|
||||
- "0.8"
|
||||
- "0.10"
|
||||
- "0.11"
|
||||
- "0.12"
|
||||
- "iojs"
|
||||
|
||||
before_install:
|
||||
- '[ "${TRAVIS_NODE_VERSION}" = "0.6" ] && npm conf set strict-ssl false || true'
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
### 0.1.3 / 2015-04-10
|
||||
|
||||
* Fix a race condition causing some fragments of deflate output to be dropped
|
||||
* Make sure to emit minimal output on all Node versions
|
||||
|
||||
### 0.1.2 / 2014-12-18
|
||||
|
||||
* Don't allow configure() to be called with unrecognized options
|
||||
|
||||
@@ -72,22 +72,21 @@ can be used to set the local session's behaviour and control that of the peer:
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2014 James Coglan
|
||||
Copyright (c) 2014-2015 James Coglan
|
||||
|
||||
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:
|
||||
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.
|
||||
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.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
||||
var fs = require('fs'),
|
||||
deflate = require('..');
|
||||
|
||||
var records = fs.readFileSync(__dirname + '/bad.out.log', 'utf8')
|
||||
.replace(/\s*$/g, '')
|
||||
.split(/\n/)
|
||||
.map(JSON.parse);
|
||||
|
||||
var client = deflate.createClientSession(),
|
||||
offer = client.generateOffer(),
|
||||
server = deflate.createServerSession([offer]),
|
||||
response = server.generateResponse(),
|
||||
compressed = [],
|
||||
size = [0, 0];
|
||||
|
||||
client.activate(response);
|
||||
|
||||
function compress(index) {
|
||||
var record = records[index];
|
||||
if (!record) {
|
||||
console.log(size, size[0] / size[1]);
|
||||
return decompress(0);
|
||||
}
|
||||
|
||||
var message = {data: new Buffer(record[3], 'base64')};
|
||||
size[0] += message.data.length;
|
||||
|
||||
server.processOutgoingMessage(message, function(error, message) {
|
||||
compressed[index] = message;
|
||||
size[1] += message.data.length;
|
||||
compress(index + 1);
|
||||
});
|
||||
}
|
||||
|
||||
function decompress(index) {
|
||||
var record = records[index];
|
||||
if (!record) return;
|
||||
|
||||
var payload = record[3],
|
||||
message = compressed[index];
|
||||
|
||||
client.processIncomingMessage(message, function(error, message) {
|
||||
var output = message.data.toString('base64');
|
||||
if (output !== payload) {
|
||||
console.error('Failed on', record, message);
|
||||
process.exit(1);
|
||||
}
|
||||
decompress(index + 1);
|
||||
});
|
||||
}
|
||||
|
||||
compress(0);
|
||||
+30
-16
@@ -3,8 +3,6 @@
|
||||
var zlib = require('zlib'),
|
||||
common = require('./common');
|
||||
|
||||
var VERSION = process.version.match(/\d+/g).map(function(n) { return parseInt(n, 10) });
|
||||
|
||||
var Session = function(options) {
|
||||
this._level = common.fetch(options, 'level', zlib.Z_DEFAULT_LEVEL);
|
||||
this._memLevel = common.fetch(options, 'memLevel', zlib.Z_DEFAULT_MEMLEVEL);
|
||||
@@ -90,19 +88,8 @@ Session.prototype.processOutgoingMessage = function(message, callback) {
|
||||
};
|
||||
|
||||
var onData = function(data) {
|
||||
var tail = data.slice(Math.max(data.length - 4, 0), data.length),
|
||||
isEnd = (tail[0] === 0x00 && tail[1] === 0x00 && tail[2] === 0xff && tail[3] === 0xff);
|
||||
|
||||
if (isEnd) data = data.slice(0, data.length - 4);
|
||||
|
||||
chunks.push(data);
|
||||
length += data.length;
|
||||
|
||||
if (isEnd) {
|
||||
message.rsv1 = true;
|
||||
message.data = common.concat(chunks, length);
|
||||
return_(null, message);
|
||||
}
|
||||
};
|
||||
|
||||
var onError = function(error) {
|
||||
@@ -111,9 +98,19 @@ Session.prototype.processOutgoingMessage = function(message, callback) {
|
||||
|
||||
deflate.on('data', onData);
|
||||
deflate.on('error', onError);
|
||||
|
||||
deflate.write(message.data);
|
||||
if (VERSION[0] === 0 && VERSION[1] < 10) deflate.flush();
|
||||
|
||||
var onFlush = function() {
|
||||
var data = common.concat(chunks, length);
|
||||
message.data = data.slice(0, data.length - 4);
|
||||
message.rsv1 = true;
|
||||
return_(null, message);
|
||||
};
|
||||
|
||||
if (deflate.params !== undefined)
|
||||
deflate.flush(zlib.Z_SYNC_FLUSH, onFlush);
|
||||
else
|
||||
deflate.flush(onFlush);
|
||||
};
|
||||
|
||||
Session.prototype.close = function() {
|
||||
@@ -135,12 +132,29 @@ Session.prototype._getDeflate = function() {
|
||||
if (this._deflate) return this._deflate;
|
||||
|
||||
var deflate = zlib.createDeflateRaw({
|
||||
flush: zlib.Z_SYNC_FLUSH,
|
||||
windowBits: this._ownWindowBits,
|
||||
level: this._level,
|
||||
memLevel: this._memLevel,
|
||||
strategy: this._strategy
|
||||
});
|
||||
|
||||
var flush = deflate.flush;
|
||||
|
||||
// This monkey-patch is needed to make Node 0.10 produce optimal output.
|
||||
// Without this it uses Z_FULL_FLUSH and effectively drops all its context
|
||||
// state on every flush.
|
||||
|
||||
if (deflate._flushFlag !== undefined && deflate.params === undefined)
|
||||
deflate.flush = function(callback) {
|
||||
var ws = this._writableState;
|
||||
if (ws.ended || ws.ending || ws.needDrain) {
|
||||
flush.call(this, callback);
|
||||
} else {
|
||||
this._flushFlag = zlib.Z_SYNC_FLUSH;
|
||||
this.write(new Buffer(0), '', callback);
|
||||
}
|
||||
};
|
||||
|
||||
if (this._ownContextTakeover) this._deflate = deflate;
|
||||
return deflate;
|
||||
};
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
, "keywords" : ["websocket", "compression", "deflate"]
|
||||
, "license" : "MIT"
|
||||
|
||||
, "version" : "0.1.2"
|
||||
, "version" : "0.1.3"
|
||||
, "engines" : {"node": ">=0.6.0"}
|
||||
, "main" : "./lib/permessage_deflate"
|
||||
, "devDependencies" : {"jstest": ""}
|
||||
|
||||
+10
-11
@@ -9,7 +9,6 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
this.deflate = zlibMock()
|
||||
this.inflate = zlibMock()
|
||||
this.flush = zlib.Z_SYNC_FLUSH
|
||||
this.level = zlib.Z_DEFAULT_LEVEL
|
||||
this.memLevel = zlib.Z_DEFAULT_MEMLEVEL
|
||||
this.strategy = zlib.Z_DEFAULT_STRATEGY
|
||||
@@ -25,7 +24,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
this.stub(stream, "removeListener")
|
||||
|
||||
this.stub(stream, "write")
|
||||
this.stub(stream, "flush", function(cb) { if(cb) cb() });
|
||||
this.stub(stream, "flush").yields([])
|
||||
this.stub(stream, "close").raises(new Error("unexpected close()"))
|
||||
|
||||
return stream
|
||||
@@ -71,7 +70,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
it("uses context takeover and 15 window bits for deflating outgoing messages", function() { with(this) {
|
||||
activate()
|
||||
expect(zlib, "createDeflateRaw").given({flush: flush, windowBits: 15, level: level, memLevel: memLevel, strategy: strategy}).exactly(1).returning(deflate)
|
||||
expect(zlib, "createDeflateRaw").given({windowBits: 15, level: level, memLevel: memLevel, strategy: strategy}).exactly(1).returning(deflate)
|
||||
processOutgoingMessage()
|
||||
processOutgoingMessage()
|
||||
}})
|
||||
@@ -102,7 +101,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
it("uses no context takeover and 15 window bits to deflate outgoing messages", function() { with(this) {
|
||||
activate()
|
||||
expect(zlib, "createDeflateRaw").given({flush: flush, windowBits: 15, level: level, memLevel: memLevel, strategy: strategy}).exactly(2).returning(deflate)
|
||||
expect(zlib, "createDeflateRaw").given({windowBits: 15, level: level, memLevel: memLevel, strategy: strategy}).exactly(2).returning(deflate)
|
||||
expect(deflate, "close").exactly(2)
|
||||
processOutgoingMessage()
|
||||
processOutgoingMessage()
|
||||
@@ -141,7 +140,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
it("uses context takeover and 8 window bits for deflating outgoing messages", function() { with(this) {
|
||||
activate()
|
||||
expect(zlib, "createDeflateRaw").given({flush: flush, windowBits: 8, level: level, memLevel: memLevel, strategy: strategy}).exactly(1).returning(deflate)
|
||||
expect(zlib, "createDeflateRaw").given({windowBits: 8, level: level, memLevel: memLevel, strategy: strategy}).exactly(1).returning(deflate)
|
||||
processOutgoingMessage()
|
||||
processOutgoingMessage()
|
||||
}})
|
||||
@@ -170,7 +169,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
it("uses no context takeover and 15 window bits for deflating outgoing messages", function() { with(this) {
|
||||
activate()
|
||||
expect(zlib, "createDeflateRaw").given({flush: flush, windowBits: 15, level: level, memLevel: memLevel, strategy: strategy}).exactly(2).returning(deflate)
|
||||
expect(zlib, "createDeflateRaw").given({windowBits: 15, level: level, memLevel: memLevel, strategy: strategy}).exactly(2).returning(deflate)
|
||||
expect(deflate, "close").exactly(2)
|
||||
processOutgoingMessage()
|
||||
processOutgoingMessage()
|
||||
@@ -192,7 +191,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
it("uses context takeover and 9 window bits for deflating outgoing messages", function() { with(this) {
|
||||
activate()
|
||||
expect(zlib, "createDeflateRaw").given({flush: flush, windowBits: 9, level: level, memLevel: memLevel, strategy: strategy}).exactly(1).returning(deflate)
|
||||
expect(zlib, "createDeflateRaw").given({windowBits: 9, level: level, memLevel: memLevel, strategy: strategy}).exactly(1).returning(deflate)
|
||||
processOutgoingMessage()
|
||||
processOutgoingMessage()
|
||||
}})
|
||||
@@ -215,7 +214,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
it("uses context takeover and 8 window bits for deflating outgoing messages", function() { with(this) {
|
||||
activate()
|
||||
expect(zlib, "createDeflateRaw").given({flush: flush, windowBits: 8, level: level, memLevel: memLevel, strategy: strategy}).exactly(1).returning(deflate)
|
||||
expect(zlib, "createDeflateRaw").given({windowBits: 8, level: level, memLevel: memLevel, strategy: strategy}).exactly(1).returning(deflate)
|
||||
processOutgoingMessage()
|
||||
processOutgoingMessage()
|
||||
}})
|
||||
@@ -310,7 +309,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
it("sets the level of the deflate stream", function() { with(this) {
|
||||
activate()
|
||||
expect(zlib, "createDeflateRaw").given({flush: flush, windowBits: 15, level: zlib.Z_BEST_SPEED, memLevel: memLevel, strategy: strategy}).returns(deflate)
|
||||
expect(zlib, "createDeflateRaw").given({windowBits: 15, level: zlib.Z_BEST_SPEED, memLevel: memLevel, strategy: strategy}).returns(deflate)
|
||||
processOutgoingMessage()
|
||||
}})
|
||||
}})
|
||||
@@ -320,7 +319,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
it("sets the memLevel of the deflate stream", function() { with(this) {
|
||||
activate()
|
||||
expect(zlib, "createDeflateRaw").given({flush: flush, windowBits: 15, level: zlib.Z_DEFAULT_LEVEL, memLevel: 5, strategy: strategy}).returns(deflate)
|
||||
expect(zlib, "createDeflateRaw").given({windowBits: 15, level: zlib.Z_DEFAULT_LEVEL, memLevel: 5, strategy: strategy}).returns(deflate)
|
||||
processOutgoingMessage()
|
||||
}})
|
||||
}})
|
||||
@@ -330,7 +329,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
it("sets the strategy of the deflate stream", function() { with(this) {
|
||||
activate()
|
||||
expect(zlib, "createDeflateRaw").given({flush: flush, windowBits: 15, level: zlib.Z_DEFAULT_LEVEL, memLevel: memLevel, strategy: zlib.Z_FILTERED}).returns(deflate)
|
||||
expect(zlib, "createDeflateRaw").given({windowBits: 15, level: zlib.Z_DEFAULT_LEVEL, memLevel: memLevel, strategy: zlib.Z_FILTERED}).returns(deflate)
|
||||
processOutgoingMessage()
|
||||
}})
|
||||
}})
|
||||
|
||||
+11
-12
@@ -9,7 +9,6 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
this.deflate = zlibMock()
|
||||
this.inflate = zlibMock()
|
||||
this.flush = zlib.Z_SYNC_FLUSH
|
||||
this.level = zlib.Z_DEFAULT_LEVEL
|
||||
this.memLevel = zlib.Z_DEFAULT_MEMLEVEL
|
||||
this.strategy = zlib.Z_DEFAULT_STRATEGY
|
||||
@@ -25,7 +24,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
this.stub(stream, "removeListener")
|
||||
|
||||
this.stub(stream, "write")
|
||||
this.stub(stream, "flush", function(cb) { if(cb) cb() });
|
||||
this.stub(stream, "flush").yields([])
|
||||
this.stub(stream, "close").raises(new Error("unexpected close()"))
|
||||
|
||||
return stream
|
||||
@@ -62,7 +61,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
it("uses context takeover and 15 window bits for deflating outgoing messages", function() { with(this) {
|
||||
response()
|
||||
expect(zlib, "createDeflateRaw").given({flush: flush, windowBits: 15, level: level, memLevel: memLevel, strategy: strategy}).exactly(1).returning(deflate)
|
||||
expect(zlib, "createDeflateRaw").given({windowBits: 15, level: level, memLevel: memLevel, strategy: strategy}).exactly(1).returning(deflate)
|
||||
processOutgoingMessage()
|
||||
processOutgoingMessage()
|
||||
}})
|
||||
@@ -77,7 +76,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
it("uses no context takeover and 15 window bits for deflating outgoing messages", function() { with(this) {
|
||||
response()
|
||||
expect(zlib, "createDeflateRaw").given({flush: flush, windowBits: 15, level: level, memLevel: memLevel, strategy: strategy}).exactly(2).returning(deflate)
|
||||
expect(zlib, "createDeflateRaw").given({windowBits: 15, level: level, memLevel: memLevel, strategy: strategy}).exactly(2).returning(deflate)
|
||||
expect(deflate, "close").exactly(2)
|
||||
processOutgoingMessage()
|
||||
processOutgoingMessage()
|
||||
@@ -109,7 +108,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
it("uses context takeover and 13 window bits for deflating outgoing messages", function() { with(this) {
|
||||
response()
|
||||
expect(zlib, "createDeflateRaw").given({flush: flush, windowBits: 13, level: level, memLevel: memLevel, strategy: strategy}).exactly(1).returning(deflate)
|
||||
expect(zlib, "createDeflateRaw").given({windowBits: 13, level: level, memLevel: memLevel, strategy: strategy}).exactly(1).returning(deflate)
|
||||
processOutgoingMessage()
|
||||
processOutgoingMessage()
|
||||
}})
|
||||
@@ -172,7 +171,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
it("uses no context takeover and 15 window bits for deflating outgoing messages", function() { with(this) {
|
||||
response()
|
||||
expect(zlib, "createDeflateRaw").given({flush: flush, windowBits: 15, level: level, memLevel: memLevel, strategy: strategy}).exactly(2).returning(deflate)
|
||||
expect(zlib, "createDeflateRaw").given({windowBits: 15, level: level, memLevel: memLevel, strategy: strategy}).exactly(2).returning(deflate)
|
||||
expect(deflate, "close").exactly(2)
|
||||
processOutgoingMessage()
|
||||
processOutgoingMessage()
|
||||
@@ -190,7 +189,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
it("uses context takeover and 12 window bits for deflating outgoing messages", function() { with(this) {
|
||||
response()
|
||||
expect(zlib, "createDeflateRaw").given({flush: flush, windowBits: 12, level: level, memLevel: memLevel, strategy: strategy}).exactly(1).returning(deflate)
|
||||
expect(zlib, "createDeflateRaw").given({windowBits: 12, level: level, memLevel: memLevel, strategy: strategy}).exactly(1).returning(deflate)
|
||||
processOutgoingMessage()
|
||||
processOutgoingMessage()
|
||||
}})
|
||||
@@ -205,7 +204,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
it("uses context takeover and 12 window bits for deflating outgoing messages", function() { with(this) {
|
||||
response()
|
||||
expect(zlib, "createDeflateRaw").given({flush: flush, windowBits: 12, level: level, memLevel: memLevel, strategy: strategy}).exactly(1).returning(deflate)
|
||||
expect(zlib, "createDeflateRaw").given({windowBits: 12, level: level, memLevel: memLevel, strategy: strategy}).exactly(1).returning(deflate)
|
||||
processOutgoingMessage()
|
||||
processOutgoingMessage()
|
||||
}})
|
||||
@@ -220,7 +219,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
it("uses context takeover and 11 window bits for deflating outgoing messages", function() { with(this) {
|
||||
response()
|
||||
expect(zlib, "createDeflateRaw").given({flush: flush, windowBits: 11, level: level, memLevel: memLevel, strategy: strategy}).exactly(1).returning(deflate)
|
||||
expect(zlib, "createDeflateRaw").given({windowBits: 11, level: level, memLevel: memLevel, strategy: strategy}).exactly(1).returning(deflate)
|
||||
processOutgoingMessage()
|
||||
processOutgoingMessage()
|
||||
}})
|
||||
@@ -312,7 +311,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
it("sets the level of the deflate stream", function() { with(this) {
|
||||
response()
|
||||
expect(zlib, "createDeflateRaw").given({flush: flush, windowBits: 15, level: zlib.Z_BEST_SPEED, memLevel: memLevel, strategy: strategy}).returns(deflate)
|
||||
expect(zlib, "createDeflateRaw").given({windowBits: 15, level: zlib.Z_BEST_SPEED, memLevel: memLevel, strategy: strategy}).returns(deflate)
|
||||
processOutgoingMessage()
|
||||
}})
|
||||
}})
|
||||
@@ -322,7 +321,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
it("sets the memLevel of the deflate stream", function() { with(this) {
|
||||
response()
|
||||
expect(zlib, "createDeflateRaw").given({flush: flush, windowBits: 15, level: zlib.Z_DEFAULT_LEVEL, memLevel: 5, strategy: strategy}).returns(deflate)
|
||||
expect(zlib, "createDeflateRaw").given({windowBits: 15, level: zlib.Z_DEFAULT_LEVEL, memLevel: 5, strategy: strategy}).returns(deflate)
|
||||
processOutgoingMessage()
|
||||
}})
|
||||
}})
|
||||
@@ -332,7 +331,7 @@ test.describe("ClientSession", function() { with(this) {
|
||||
|
||||
it("sets the strategy of the deflate stream", function() { with(this) {
|
||||
response()
|
||||
expect(zlib, "createDeflateRaw").given({flush: flush, windowBits: 15, level: zlib.Z_DEFAULT_LEVEL, memLevel: memLevel, strategy: zlib.Z_FILTERED}).returns(deflate)
|
||||
expect(zlib, "createDeflateRaw").given({windowBits: 15, level: zlib.Z_DEFAULT_LEVEL, memLevel: memLevel, strategy: zlib.Z_FILTERED}).returns(deflate)
|
||||
processOutgoingMessage()
|
||||
}})
|
||||
}})
|
||||
|
||||
Reference in New Issue
Block a user