gtp

package
v0.1.25 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 22, 2024 License: LGPL-2.1 Imports: 12 Imported by: 0

Documentation

Overview

Package gtp Golaxy传输层协议(golaxy transfer protocol),适用于长连接、实时通信的工作场景,需要工作在可靠网络协议(tcp/kcp)之上,支持链路加密、链路鉴权、断线续连等特性。

  • 关于加密,支持秘钥交换(ECDHE)、签名与验证,不支持证书验证,对于安全性要求极高的应用场景,应该使用TLS协议直接加密链路,并关闭本协议的数据加密选项。
  • 关于断线续联,支持下层协议断连后,双端缓存待发送的消息包,待使用新连接重连后继续收发消息。
  • 支持消息包压缩。
  • 支持可靠的消息包传输时序。
  • 支持新增自定义消息。
  • 协议适用于网络游戏、远程控制和传感器交互等实时性要求较高的场景。
  • 为了提高性能,本层所有消息中[]byte和string类型字段,应该使用ReadBytesRef和ReadStringRef读取。

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNotRegistered = errors.New("gtp: msg not registered") // 消息未注册
)

Functions

This section is empty.

Types

type AsymmetricEncryption

type AsymmetricEncryption uint8

AsymmetricEncryption 非对称加密算法

const (
	AsymmetricEncryption_None       AsymmetricEncryption = iota // 未设置
	AsymmetricEncryption_RSA_256                                // RSA-256算法
	AsymmetricEncryption_ECDSA_P256                             // ECDSA-NIST-P256算法
)

type BlockCipherMode

type BlockCipherMode uint8

BlockCipherMode 分组密码工作模式

const (
	BlockCipherMode_None BlockCipherMode = iota // 未设置
	BlockCipherMode_CTR                         // CTR模式
	BlockCipherMode_CBC                         // CBC模式
	BlockCipherMode_CFB                         // CFB模式
	BlockCipherMode_OFB                         // OFB模式
	BlockCipherMode_GCM                         // GCM模式
)

func (BlockCipherMode) IV

func (bcm BlockCipherMode) IV() bool

IV 是否需要iv,iv大小与加密算法的blocksize相同

func (BlockCipherMode) Nonce

func (bcm BlockCipherMode) Nonce() bool

Nonce 是否需要nonce,nonce大小与加密算法的blocksize相同

func (BlockCipherMode) Padding

func (bcm BlockCipherMode) Padding() bool

Padding 是否需要分组对齐填充数据

type CipherSuite

type CipherSuite struct {
	SecretKeyExchange   SecretKeyExchange   // 秘钥交换函数
	SymmetricEncryption SymmetricEncryption // 对称加密算法
	BlockCipherMode     BlockCipherMode     // 分组密码工作模式
	PaddingMode         PaddingMode         // 填充方案
	MACHash             Hash                // MAC摘要函数
}

CipherSuite 密码学套件

func (CipherSuite) Read

func (cs CipherSuite) Read(p []byte) (int, error)

Read implements io.Reader

func (CipherSuite) Size

func (CipherSuite) Size() int

Size 大小

func (*CipherSuite) Write

func (cs *CipherSuite) Write(p []byte) (int, error)

Write implements io.Writer

type Code

type Code int32

Code 错误码

const (
	Code_VersionError    Code = iota + 1 // 版本错误
	Code_SessionNotFound                 // Session未找到
	Code_EncryptFailed                   // 加密失败
	Code_AuthFailed                      // 鉴权失败
	Code_ContinueFailed                  // 重连失败
	Code_Reject                          // 拒绝连接
	Code_Shutdown                        // 服务关闭
	Code_SessionDeath                    // 会话过期
	Code_Customize                       // 自定义错误码起点
)

type Compression

type Compression uint8

Compression 压缩函数

const (
	Compression_None    Compression = iota // 未设置
	Compression_Gzip                       // Gzip压缩算法
	Compression_Deflate                    // Deflate压缩算法
	Compression_Brotli                     // Brotli压缩算法
	Compression_LZ4                        // LZ4压缩算法
	Compression_Snappy                     // Snappy压缩算法
)

type Flag

type Flag = uint8

Flag 标志位

const (
	Flag_EncryptOK  Flag = 1 << (iota + Flag_Customize) // 加密成功,在服务端发起的Finished消息携带
	Flag_AuthOK                                         // 鉴权成功,在服务端发起的Finished消息携带
	Flag_ContinueOK                                     // 断线重连成功,在服务端发起的Finished消息携带
)

Finished消息标志位

const (
	Flag_Ping Flag = 1 << (iota + Flag_Customize) // 心跳ping
	Flag_Pong                                     // 心跳pong
)

Heartbeat消息标志位

const (
	Flag_HelloDone  Flag = 1 << (iota + Flag_Customize) // Hello完成,在服务端返回的Hello消息中携带,表示初步认可客户端连接
	Flag_Encryption                                     // 开启加密(协议优先考虑性能,要求安全性请直接使用TLS加密链路),在服务端返回的Hello消息中携带,表示链路需要加密,需要执行秘钥交换流程
	Flag_Auth                                           // 开启鉴权(基于token鉴权),在服务端返回的Hello消息中携带,表示链路需要认证,需要执行鉴权流程
	Flag_Continue                                       // 断线重连
)

Hello消息标志位

const (
	Flag_ReqTime  Flag = 1 << (iota + Flag_Customize) // 请求同步时间
	Flag_RespTime                                     // 响应同步时间
)

SyncTime消息标志位

const (
	Flag_Encrypted  Flag   = 1 << iota // 已加密
	Flag_MAC                           // 有MAC
	Flag_Compressed                    // 已压缩
	Flag_Customize  = iota             // 自定义标志位起点
)

固定标志位

const (
	Flag_Signature Flag = 1 << (iota + Flag_Customize) // 有签名数据,在双方的ECDHE秘钥交换消息中携带,表示可以验证对方签名
)

ECDHESecretKeyExchange消息标志位

const (
	Flag_VerifyEncryption Flag = 1 << (iota + Flag_Customize) // 交换秘钥后,在双方变更密码规范消息中携带,表示需要验证加密是否成功
)

MsgChangeCipherSpec消息标志位

type Flags

type Flags uint8

Flags 所有标志位

func Flags_None

func Flags_None() Flags

func (Flags) Is

func (f Flags) Is(b Flag) bool

Is 判断标志位

func (*Flags) Set

func (f *Flags) Set(b Flag, v bool) *Flags

Set 设置标志位

func (Flags) Setd

func (f Flags) Setd(b Flag, v bool) Flags

Setd 拷贝并设置标志位

type Hash

type Hash uint8

Hash 摘要函数

const (
	Hash_None     Hash = iota // 未设置
	Hash_Fnv1a32              // Fnv-1a 32bit算法(用于MAC)
	Hash_Fnv1a64              // Fnv-1a 64bit算法(用于MAC)
	Hash_Fnv1a128             // Fnv-1a 128bit算法(用于MAC)
	Hash_SHA256               // SHA256算法(用于非对称加密或MAC)
)

func (Hash) Bits

func (h Hash) Bits() int

Bits 位数

type IMsgCreator

type IMsgCreator interface {
	// Register 注册消息
	Register(msg Msg)
	// Deregister 取消注册消息
	Deregister(msgId MsgId)
	// New 创建消息指针
	New(msgId MsgId) (Msg, error)
}

IMsgCreator 消息对象构建器接口

func DefaultMsgCreator

func DefaultMsgCreator() IMsgCreator

DefaultMsgCreator 默认消息对象构建器

func NewMsgCreator

func NewMsgCreator() IMsgCreator

NewMsgCreator 创建消息对象构建器

type Msg

type Msg interface {
	MsgReader
	MsgWriter
	// Clone 克隆消息对象
	Clone() Msg
}

Msg 消息接口

type MsgAuth

type MsgAuth struct {
	UserId     string // 用户Id
	Token      string // 令牌
	Extensions []byte // 扩展内容
}

MsgAuth 鉴权(注意:为了提高解码性能,减少内存碎片,解码string与bytes字段时均使用引用类型,引用字节池中的bytes,GC时会被归还字节池,不要直接持有此类型字段)

func (*MsgAuth) Clone

func (m *MsgAuth) Clone() Msg

Clone 克隆消息对象

func (MsgAuth) MsgId

func (MsgAuth) MsgId() MsgId

MsgId 消息Id

func (MsgAuth) Read

func (m MsgAuth) Read(p []byte) (int, error)

Read implements io.Reader

func (MsgAuth) Size

func (m MsgAuth) Size() int

Size 大小

func (*MsgAuth) Write

func (m *MsgAuth) Write(p []byte) (int, error)

Write implements io.Writer

type MsgChangeCipherSpec

type MsgChangeCipherSpec struct {
	EncryptedHello []byte // 加密Hello消息,用于双方验证加密是否成功
}

MsgChangeCipherSpec 变更密码规范(注意:为了提高解码性能,减少内存碎片,解码string与bytes字段时均使用引用类型,引用字节池中的bytes,GC时会被归还字节池,不要直接持有此类型字段)

func (*MsgChangeCipherSpec) Clone

func (m *MsgChangeCipherSpec) Clone() Msg

Clone 克隆消息对象

func (MsgChangeCipherSpec) MsgId

func (MsgChangeCipherSpec) MsgId() MsgId

MsgId 消息Id

func (MsgChangeCipherSpec) Read

func (m MsgChangeCipherSpec) Read(p []byte) (int, error)

Read implements io.Reader

func (MsgChangeCipherSpec) Size

func (m MsgChangeCipherSpec) Size() int

Size 大小

func (*MsgChangeCipherSpec) Write

func (m *MsgChangeCipherSpec) Write(p []byte) (int, error)

Write implements io.Writer

type MsgCompressed

type MsgCompressed struct {
	Data         []byte
	OriginalSize int64
}

MsgCompressed 压缩消息

func (MsgCompressed) Read

func (m MsgCompressed) Read(p []byte) (int, error)

Read implements io.Reader

func (MsgCompressed) Size

func (m MsgCompressed) Size() int

Size 大小

func (*MsgCompressed) Write

func (m *MsgCompressed) Write(p []byte) (int, error)

Write implements io.Writer

type MsgContinue

type MsgContinue struct {
	SendSeq uint32 // 客户端请求消息序号
	RecvSeq uint32 // 客户端响应消息序号
}

MsgContinue 重连

func (MsgContinue) Clone

func (m MsgContinue) Clone() Msg

Clone 克隆消息对象

func (MsgContinue) MsgId

func (MsgContinue) MsgId() MsgId

MsgId 消息Id

func (MsgContinue) Read

func (m MsgContinue) Read(p []byte) (int, error)

Read implements io.Reader

func (MsgContinue) Size

func (MsgContinue) Size() int

Size 大小

func (*MsgContinue) Write

func (m *MsgContinue) Write(p []byte) (int, error)

Write implements io.Writer

type MsgECDHESecretKeyExchange

type MsgECDHESecretKeyExchange struct {
	NamedCurve         NamedCurve         // 曲线类型
	PublicKey          []byte             // 公钥
	IV                 []byte             // iv
	Nonce              []byte             // nonce
	NonceStep          []byte             // nonce step
	SignatureAlgorithm SignatureAlgorithm // 签名算法
	Signature          []byte             // 签名
}

MsgECDHESecretKeyExchange ECDHE秘钥交换消息,利用(g^a mod p)^b mod p == (g^b mod p)^a mod p等式,交换秘钥 (注意:为了提高解码性能,减少内存碎片,解码string与bytes字段时均使用引用类型,引用字节池中的bytes,GC时会被归还字节池,不要直接持有此类型字段)

func (*MsgECDHESecretKeyExchange) Clone

func (m *MsgECDHESecretKeyExchange) Clone() Msg

Clone 克隆消息对象

func (MsgECDHESecretKeyExchange) MsgId

MsgId 消息Id

func (MsgECDHESecretKeyExchange) Read

func (m MsgECDHESecretKeyExchange) Read(p []byte) (int, error)

Read implements io.Reader

func (MsgECDHESecretKeyExchange) Size

func (m MsgECDHESecretKeyExchange) Size() int

Size 大小

func (*MsgECDHESecretKeyExchange) Write

func (m *MsgECDHESecretKeyExchange) Write(p []byte) (int, error)

Write implements io.Writer

type MsgFinished

type MsgFinished struct {
	SendSeq uint32 // 服务端请求序号
	RecvSeq uint32 // 服务端响应序号
}

MsgFinished 握手结束,表示认可对端,可以开始传输数据

func (MsgFinished) Clone

func (m MsgFinished) Clone() Msg

Clone 克隆消息对象

func (MsgFinished) MsgId

func (MsgFinished) MsgId() MsgId

MsgId 消息Id

func (MsgFinished) Read

func (m MsgFinished) Read(p []byte) (int, error)

Read implements io.Reader

func (MsgFinished) Size

func (MsgFinished) Size() int

Size 大小

func (*MsgFinished) Write

func (m *MsgFinished) Write(p []byte) (int, error)

Write implements io.Writer

type MsgHead

type MsgHead struct {
	Len   uint32 // 消息包长度
	MsgId MsgId  // 消息Id
	Flags Flags  // 标志位
	Seq   uint32 // 消息序号
	Ack   uint32 // 应答序号
}

MsgHead 消息头

func (MsgHead) Read

func (m MsgHead) Read(p []byte) (int, error)

Read implements io.Reader

func (MsgHead) Size

func (MsgHead) Size() int

Size 大小

func (*MsgHead) Write

func (m *MsgHead) Write(p []byte) (int, error)

Write implements io.Writer

type MsgHeartbeat

type MsgHeartbeat struct{}

MsgHeartbeat 心跳,消息体为空,可以不解析

func (MsgHeartbeat) Clone

func (m MsgHeartbeat) Clone() Msg

Clone 克隆消息对象

func (MsgHeartbeat) MsgId

func (MsgHeartbeat) MsgId() MsgId

MsgId 消息Id

func (MsgHeartbeat) Read

func (MsgHeartbeat) Read(p []byte) (int, error)

Read implements io.Reader

func (MsgHeartbeat) Size

func (MsgHeartbeat) Size() int

Size 大小

func (MsgHeartbeat) Write

func (MsgHeartbeat) Write(p []byte) (int, error)

Write implements io.Writer

type MsgHello

type MsgHello struct {
	Version     Version     // 协议版本
	SessionId   string      // 会话Id,如果客户端上传空值,服务端将会分配新会话,如果非空值,服务端将尝试查找会话,查找失败会重置链路
	Random      []byte      // 随机数,用于秘钥交换
	CipherSuite CipherSuite // 密码学套件,客户端提交的密码学套件建议,服务端可能不采纳,以服务端返回的为准,若客户端不支持,直接切断链路
	Compression Compression // 压缩函数,客户端提交的压缩函数建议,服务端可能不采纳,以服务端返回的为准,若客户端不支持,直接切断链路
}

MsgHello Hello消息(注意:为了提高解码性能,减少内存碎片,解码string与bytes字段时均使用引用类型,引用字节池中的bytes,GC时会被归还字节池,不要直接持有此类型字段)

func (*MsgHello) Clone

func (m *MsgHello) Clone() Msg

Clone 克隆消息对象

func (MsgHello) MsgId

func (MsgHello) MsgId() MsgId

MsgId 消息Id

func (MsgHello) Read

func (m MsgHello) Read(p []byte) (int, error)

Read implements io.Reader

func (MsgHello) Size

func (m MsgHello) Size() int

Size 大小

func (*MsgHello) Write

func (m *MsgHello) Write(p []byte) (int, error)

Write implements io.Writer

type MsgId

type MsgId = uint8

MsgId 消息Id

const (
	MsgId_None                   MsgId = iota // 未设置
	MsgId_Hello                               // Hello Handshake C<->S 不加密
	MsgId_ECDHESecretKeyExchange              // ECDHE秘钥交换 Handshake S<->C 不加密
	MsgId_ChangeCipherSpec                    // 变更密码规范 Handshake S<->C 不加密
	MsgId_Auth                                // 鉴权 Handshake C->S 加密
	MsgId_Continue                            // 重连 Handshake C->S 加密
	MsgId_Finished                            // 握手结束 Handshake S<->C 加密
	MsgId_Rst                                 // 重置链路 Ctrl S->C 加密
	MsgId_Heartbeat                           // 心跳 Ctrl C<->S or S<->C 加密
	MsgId_SyncTime                            // 时钟同步 Ctrl C<->S 加密
	MsgId_Payload                             // 数据传输 Trans C<->S or S<->C 加密
	MsgId_Customize              = 16         // 自定义消息起点
)

type MsgMAC

type MsgMAC struct {
	Data []byte
	MAC  []byte
}

MsgMAC 包含MAC消息

func (MsgMAC) Read

func (m MsgMAC) Read(p []byte) (int, error)

Read implements io.Reader

func (MsgMAC) Size

func (m MsgMAC) Size() int

Size 大小

func (*MsgMAC) Write

func (m *MsgMAC) Write(p []byte) (int, error)

Write implements io.Writer

type MsgMAC32

type MsgMAC32 struct {
	Data []byte
	MAC  uint32
}

MsgMAC32 包含MAC(32bit)消息

func (MsgMAC32) Read

func (m MsgMAC32) Read(p []byte) (int, error)

Read implements io.Reader

func (MsgMAC32) Size

func (m MsgMAC32) Size() int

Size 大小

func (*MsgMAC32) Write

func (m *MsgMAC32) Write(p []byte) (int, error)

Write implements io.Writer

type MsgMAC64

type MsgMAC64 struct {
	Data []byte
	MAC  uint64
}

MsgMAC64 包含MAC(64bit)消息

func (MsgMAC64) Read

func (m MsgMAC64) Read(p []byte) (int, error)

Read implements io.Reader

func (MsgMAC64) Size

func (m MsgMAC64) Size() int

Size 大小

func (*MsgMAC64) Write

func (m *MsgMAC64) Write(p []byte) (int, error)

Write implements io.Writer

type MsgPacket

type MsgPacket struct {
	Head MsgHead // 消息头
	Msg  Msg     // 消息
}

MsgPacket 消息包

func (MsgPacket) Read

func (mp MsgPacket) Read(p []byte) (int, error)

Read implements io.Reader

func (MsgPacket) Size

func (mp MsgPacket) Size() int

Size 大小

func (*MsgPacket) Write

func (mp *MsgPacket) Write(p []byte) (int, error)

Write implements io.Writer

type MsgPacketLen

type MsgPacketLen struct {
	Len uint32 // 消息包长度
}

MsgPacketLen 消息包长度

func (MsgPacketLen) Read

func (m MsgPacketLen) Read(p []byte) (int, error)

Read implements io.Reader

func (MsgPacketLen) Size

func (MsgPacketLen) Size() int

Size 大小

func (*MsgPacketLen) Write

func (m *MsgPacketLen) Write(p []byte) (int, error)

Write implements io.Writer

type MsgPayload

type MsgPayload struct {
	Data []byte // 数据
}

MsgPayload 数据传输(注意:为了提高解码性能,减少内存碎片,解码string与bytes字段时均使用引用类型,引用字节池中的bytes,GC时会被归还字节池,不要直接持有此类型字段)

func (*MsgPayload) Clone

func (m *MsgPayload) Clone() Msg

Clone 克隆消息对象

func (MsgPayload) MsgId

func (MsgPayload) MsgId() MsgId

MsgId 消息Id

func (MsgPayload) Read

func (m MsgPayload) Read(p []byte) (int, error)

Read implements io.Reader

func (MsgPayload) Size

func (m MsgPayload) Size() int

Size 大小

func (*MsgPayload) Write

func (m *MsgPayload) Write(p []byte) (int, error)

Write implements io.Writer

type MsgReader

type MsgReader interface {
	io.Reader
	// Size 大小
	Size() int
	// MsgId 消息Id
	MsgId() MsgId
}

MsgReader 读取消息

type MsgRst

type MsgRst struct {
	Code    Code   // 错误码
	Message string // 错误信息
}

MsgRst 重置链路(注意:为了提高解码性能,减少内存碎片,解码string与bytes字段时均使用引用类型,引用字节池中的bytes,GC时会被归还字节池,不要直接持有此类型字段)

func (*MsgRst) Clone

func (m *MsgRst) Clone() Msg

Clone 克隆消息对象

func (MsgRst) MsgId

func (MsgRst) MsgId() MsgId

MsgId 消息Id

func (MsgRst) Read

func (m MsgRst) Read(p []byte) (int, error)

Read implements io.Reader

func (MsgRst) Size

func (m MsgRst) Size() int

Size 大小

func (*MsgRst) Write

func (m *MsgRst) Write(p []byte) (int, error)

Write implements io.Writer

type MsgSyncTime

type MsgSyncTime struct {
	CorrId          int64 // 关联Id,用于支持Future等异步模型
	LocalUnixMilli  int64 // 本地时间
	RemoteUnixMilli int64 // 对端时间(响应时有效)
}

MsgSyncTime 同步时间

func (MsgSyncTime) Clone

func (m MsgSyncTime) Clone() Msg

Clone 克隆消息对象

func (MsgSyncTime) MsgId

func (MsgSyncTime) MsgId() MsgId

MsgId 消息Id

func (MsgSyncTime) Read

func (m MsgSyncTime) Read(p []byte) (int, error)

Read implements io.Reader

func (MsgSyncTime) Size

func (MsgSyncTime) Size() int

Size 大小

func (*MsgSyncTime) Write

func (m *MsgSyncTime) Write(p []byte) (int, error)

Write implements io.Writer

type MsgWriter

type MsgWriter interface {
	io.Writer
	// Size 大小
	Size() int
	// MsgId 消息Id
	MsgId() MsgId
}

MsgWriter 写入消息

type NamedCurve

type NamedCurve uint8

NamedCurve 曲线类型

const (
	NamedCurve_None   NamedCurve = iota // 未设置
	NamedCurve_X25519                   // 曲线x25519
	NamedCurve_P256                     // 曲线NIST-P256
)

type PaddingMode

type PaddingMode uint8

PaddingMode 数据填充方案

const (
	PaddingMode_None     PaddingMode = iota // 未设置
	PaddingMode_Pkcs7                       // pkcs7方案(用于对称加密)
	PaddingMode_X923                        // x927方案(用于对称加密)
	PaddingMode_Pkcs1v15                    // Pkcs1v15方案(用于非对称加密RSA算法)
	PaddingMode_PSS                         // PSS方案(用于非对称加密RSA算法)
)

type SecretKeyExchange

type SecretKeyExchange uint8

SecretKeyExchange 秘钥交换函数

const (
	SecretKeyExchange_None  SecretKeyExchange = iota // 未设置
	SecretKeyExchange_ECDHE                          // ECDHE算法
)

type SignatureAlgorithm

type SignatureAlgorithm struct {
	AsymmetricEncryption AsymmetricEncryption // 非对称加密算法
	PaddingMode          PaddingMode          // 填充方案
	Hash                 Hash                 // 摘要函数
}

SignatureAlgorithm 签名算法

func (SignatureAlgorithm) Read

func (sa SignatureAlgorithm) Read(p []byte) (int, error)

Read implements io.Reader

func (SignatureAlgorithm) Size

func (SignatureAlgorithm) Size() int

Size 大小

func (*SignatureAlgorithm) Write

func (sa *SignatureAlgorithm) Write(p []byte) (int, error)

Write implements io.Writer

type SymmetricEncryption

type SymmetricEncryption uint8

SymmetricEncryption 对称加密算法

const (
	SymmetricEncryption_None               SymmetricEncryption = iota // 未设置
	SymmetricEncryption_AES                                           // AES算法
	SymmetricEncryption_ChaCha20                                      // ChaCha20算法
	SymmetricEncryption_XChaCha20                                     // XChaCha20算法
	SymmetricEncryption_ChaCha20_Poly1305                             // ChaCha20-Poly1305算法
	SymmetricEncryption_XChaCha20_Poly1305                            // XChaCha20-Poly1305算法
)

func (SymmetricEncryption) BlockCipherMode

func (se SymmetricEncryption) BlockCipherMode() bool

BlockCipherMode 是否需要分组密码工作模式配合

func (SymmetricEncryption) BlockSize

func (se SymmetricEncryption) BlockSize() (int, bool)

BlockSize 获取block大小

func (SymmetricEncryption) IV

func (se SymmetricEncryption) IV() (int, bool)

IV 获取iv大小和是否需要iv,iv大小为0表示与加密算法的blocksize相同

func (SymmetricEncryption) Nonce

func (se SymmetricEncryption) Nonce() (int, bool)

Nonce 是否需要nonce,nonce大小为0表示与加密算法的blocksize相同

type Version

type Version uint16

Version 协议版本

const (
	Version_V1_0 Version = 0x0100 // 协议v1.0版本
)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL